# coding: utf-8 # /*########################################################################## # Copyright (C) 2004-2017 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # ######################################################################### */ """This module defines a table widget that is specialized in displaying fit parameter results and associated constraints.""" __authors__ = ["V.A. Sole", "P. Knobel"] __license__ = "MIT" __date__ = "25/11/2016" import sys from collections import OrderedDict from silx.gui import qt from silx.gui.widgets.TableWidget import TableWidget def float_else_zero(sstring): """Return converted string to float. If conversion fail, return zero. :param sstring: String to be converted :return: ``float(sstrinq)`` if ``sstring`` can be converted to float (e.g. ``"3.14"``), else ``0`` """ try: return float(sstring) except ValueError: return 0 class QComboTableItem(qt.QComboBox): """:class:`qt.QComboBox` augmented with a ``sigCellChanged`` signal to emit a tuple of ``(row, column)`` coordinates when the value is changed. This signal can be used to locate the modified combo box in a table. :param row: Row number of the table cell containing this widget :param col: Column number of the table cell containing this widget""" sigCellChanged = qt.Signal(int, int) """Signal emitted when this ``QComboBox`` is activated. A ``(row, column)`` tuple is passed.""" def __init__(self, parent=None, row=None, col=None): self._row = row self._col = col qt.QComboBox.__init__(self, parent) self.activated[int].connect(self._cellChanged) def _cellChanged(self, idx): # noqa self.sigCellChanged.emit(self._row, self._col) class QCheckBoxItem(qt.QCheckBox): """:class:`qt.QCheckBox` augmented with a ``sigCellChanged`` signal to emit a tuple of ``(row, column)`` coordinates when the check box has been clicked on. This signal can be used to locate the modified check box in a table. :param row: Row number of the table cell containing this widget :param col: Column number of the table cell containing this widget""" sigCellChanged = qt.Signal(int, int) """Signal emitted when this ``QCheckBox`` is clicked. A ``(row, column)`` tuple is passed.""" def __init__(self, parent=None, row=None, col=None): self._row = row self._col = col qt.QCheckBox.__init__(self, parent) self.clicked.connect(self._cellChanged) def _cellChanged(self): self.sigCellChanged.emit(self._row, self._col) class Parameters(TableWidget): """:class:`TableWidget` customized to display fit results and to interact with :class:`FitManager` objects. Data and references to cell widgets are kept in a dictionary attribute :attr:`parameters`. :param parent: Parent widget :param labels: Column headers. If ``None``, default headers will be used. :type labels: List of strings or None :param paramlist: List of fit parameters to be displayed for each fitted peak. :type paramlist: list[str] or None """ def __init__(self, parent=None, paramlist=None): TableWidget.__init__(self, parent) self.setContentsMargins(0, 0, 0, 0) labels = ['Parameter', 'Estimation', 'Fit Value', 'Sigma', 'Constraints', 'Min/Parame', 'Max/Factor/Delta'] tooltips = ["Fit parameter name", "Estimated value for fit parameter. You can edit this column.", "Actual value for parameter, after fit", "Uncertainty (same unit as the parameter)", "Constraint to be applied to the parameter for fit", "First parameter for constraint (name of another param or min value)", "Second parameter for constraint (max value, or factor/delta)"] self.columnKeys = ['name', 'estimation', 'fitresult', 'sigma', 'code', 'val1', 'val2'] """This list assigns shorter keys to refer to columns than the displayed labels.""" self.__configuring = False # column headers and associated tooltips self.setColumnCount(len(labels)) for i, label in enumerate(labels): item = self.horizontalHeaderItem(i) if item is None: item = qt.QTableWidgetItem(label, qt.QTableWidgetItem.Type) self.setHorizontalHeaderItem(i, item) item.setText(label) if tooltips is not None: item.setToolTip(tooltips[i]) # resize columns for col_key in ["name", "estimation", "sigma", "val1", "val2"]: col_idx = self.columnIndexByField(col_key) self.resizeColumnToContents(col_idx) # Initialize the table with one line per supplied parameter paramlist = paramlist if paramlist is not None else [] self.parameters = OrderedDict() """This attribute stores all the data in an ordered dictionary. New data can be added using :meth:`newParameterLine`. Existing data can be modified using :meth:`configureLine` Keys of the dictionary are: - 'name': parameter name - 'line': line index for the parameter in the table - 'estimation' - 'fitresult' - 'sigma' - 'code': constraint code (one of the elements of :attr:`code_options`) - 'val1': first parameter related to constraint, formatted as a string, as typed in the table - 'val2': second parameter related to constraint, formatted as a string, as typed in the table - 'cons1': scalar representation of 'val1' (e.g. when val1 is the name of a fit parameter, cons1 will be the line index of this parameter) - 'cons2': scalar representation of 'val2' - 'vmin': equal to 'val1' when 'code' is "QUOTED" - 'vmax': equal to 'val2' when 'code' is "QUOTED" - 'relatedto': name of related parameter when this parameter is constrained to another parameter (same as 'val1') - 'factor': same as 'val2' when 'code' is 'FACTOR' - 'delta': same as 'val2' when 'code' is 'DELTA' - 'sum': same as 'val2' when 'code' is 'SUM' - 'group': group index for the parameter - 'xmin': data range minimum - 'xmax': data range maximum """ for line, param in enumerate(paramlist): self.newParameterLine(param, line) self.code_options = ["FREE", "POSITIVE", "QUOTED", "FIXED", "FACTOR", "DELTA", "SUM", "IGNORE", "ADD"] """Possible values in the combo boxes in the 'Constraints' column. """ # connect signal self.cellChanged[int, int].connect(self.onCellChanged) def newParameterLine(self, param, line): """Add a line to the :class:`QTableWidget`. Each line represents one of the fit parameters for one of the fitted peaks. :param param: Name of the fit parameter :type param: str :param line: 0-based line index :type line: int """ # get current number of lines nlines = self.rowCount() self.__configuring = True if line >= nlines: self.setRowCount(line + 1) # default configuration for fit parameters self.parameters[param] = OrderedDict((('line', line), ('estimation', '0'), ('fitresult', ''), ('sigma', ''), ('code', 'FREE'), ('val1', ''), ('val2', ''), ('cons1', 0), ('cons2', 0), ('vmin', '0'), ('vmax', '1'), ('relatedto', ''), ('factor', '1.0'), ('delta', '0.0'), ('sum', '0.0'), ('group', ''), ('name', param), ('xmin', None), ('xmax', None))) self.setReadWrite(param, 'estimation') self.setReadOnly(param, ['name', 'fitresult', 'sigma', 'val1', 'val2']) # Constraint codes a = [] for option in self.code_options: a.append(option) code_column_index = self.columnIndexByField('code') cellWidget = self.cellWidget(line, code_column_index) if cellWidget is None: cellWidget = QComboTableItem(self, row=line, col=code_column_index) cellWidget.addItems(a) self.setCellWidget(line, code_column_index, cellWidget) cellWidget.sigCellChanged[int, int].connect(self.onCellChanged) self.parameters[param]['code_item'] = cellWidget self.parameters[param]['relatedto_item'] = None self.__configuring = False def columnIndexByField(self, field): """ :param field: Field name (column key) :return: Index of the column with this field name """ return self.columnKeys.index(field) def fillFromFit(self, fitresults): """Fill table with values from a list of dictionaries (see :attr:`silx.math.fit.fitmanager.FitManager.fit_results`) :param fitresults: List of parameters as recorded in the ``paramlist`` attribute of a :class:`FitManager` object :type fitresults: list[dict] """ self.setRowCount(len(fitresults)) # Reinitialize and fill self.parameters self.parameters = OrderedDict() for (line, param) in enumerate(fitresults): self.newParameterLine(param['name'], line) for param in fitresults: name = param['name'] code = str(param['code']) if code not in self.code_options: # convert code from int to descriptive string code = self.code_options[int(code)] val1 = param['cons1'] val2 = param['cons2'] estimation = param['estimation'] group = param['group'] sigma = param['sigma'] fitresult = param['fitresult'] xmin = param.get('xmin') xmax = param.get('xmax') self.configureLine(name=name, code=code, val1=val1, val2=val2, estimation=estimation, fitresult=fitresult, sigma=sigma, group=group, xmin=xmin, xmax=xmax) def getConfiguration(self): """Return ``FitManager.paramlist`` dictionary encapsulated in another dictionary""" return {'parameters': self.getFitResults()} def setConfiguration(self, ddict): """Fill table with values from a ``FitManager.paramlist`` dictionary encapsulated in another dictionary""" self.fillFromFit(ddict['parameters']) def getFitResults(self): """Return fit parameters as a list of dictionaries in the format used by :class:`FitManager` (attribute ``paramlist``). """ fitparameterslist = [] for param in self.parameters: fitparam = {} name = param estimation, [code, cons1, cons2] = self.getEstimationConstraints(name) buf = str(self.parameters[param]['fitresult']) xmin = self.parameters[param]['xmin'] xmax = self.parameters[param]['xmax'] if len(buf): fitresult = float(buf) else: fitresult = 0.0 buf = str(self.parameters[param]['sigma']) if len(buf): sigma = float(buf) else: sigma = 0.0 buf = str(self.parameters[param]['group']) if len(buf): group = float(buf) else: group = 0 fitparam['name'] = name fitparam['estimation'] = estimation fitparam['fitresult'] = fitresult fitparam['sigma'] = sigma fitparam['group'] = group fitparam['code'] = code fitparam['cons1'] = cons1 fitparam['cons2'] = cons2 fitparam['xmin'] = xmin fitparam['xmax'] = xmax fitparameterslist.append(fitparam) return fitparameterslist def onCellChanged(self, row, col): """Slot called when ``cellChanged`` signal is emitted. Checks the validity of the new text in the cell, then calls :meth:`configureLine` to update the internal ``self.parameters`` dictionary. :param row: Row number of the changed cell (0-based index) :param col: Column number of the changed cell (0-based index) """ if (col != self.columnIndexByField("code")) and (col != -1): if row != self.currentRow(): return if col != self.currentColumn(): return if self.__configuring: return param = list(self.parameters)[row] field = self.columnKeys[col] oldvalue = self.parameters[param][field] if col != 4: item = self.item(row, col) if item is not None: newvalue = item.text() else: newvalue = '' else: # this is the combobox widget = self.cellWidget(row, col) newvalue = widget.currentText() if self.validate(param, field, oldvalue, newvalue): paramdict = {"name": param, field: newvalue} self.configureLine(**paramdict) else: if field == 'code': # New code not valid, try restoring the old one index = self.code_options.index(oldvalue) self.__configuring = True try: self.parameters[param]['code_item'].setCurrentIndex(index) finally: self.__configuring = False else: paramdict = {"name": param, field: oldvalue} self.configureLine(**paramdict) def validate(self, param, field, oldvalue, newvalue): """Check validity of ``newvalue`` when a cell's value is modified. :param param: Fit parameter name :param field: Column name :param oldvalue: Cell value before change attempt :param newvalue: New value to be validated :return: True if new cell value is valid, else False """ if field == 'code': return self.setCodeValue(param, oldvalue, newvalue) # FIXME: validate() shouldn't have side effects. Move this bit to configureLine()? if field == 'val1' and str(self.parameters[param]['code']) in ['DELTA', 'FACTOR', 'SUM']: _, candidates = self.getRelatedCandidates(param) # We expect val1 to be a fit parameter name if str(newvalue) in candidates: return True else: return False # except for code, val1 and name (which is read-only and does not need # validation), all fields must always be convertible to float else: try: float(str(newvalue)) except ValueError: return False return True def setCodeValue(self, param, oldvalue, newvalue): """Update 'code' and 'relatedto' fields when code cell is changed. :param param: Fit parameter name :param oldvalue: Cell value before change attempt :param newvalue: New value to be validated :return: ``True`` if code was successfully updated """ if str(newvalue) in ['FREE', 'POSITIVE', 'QUOTED', 'FIXED']: self.configureLine(name=param, code=newvalue) if str(oldvalue) == 'IGNORE': self.freeRestOfGroup(param) return True elif str(newvalue) in ['FACTOR', 'DELTA', 'SUM']: # I should check here that some parameter is set best, candidates = self.getRelatedCandidates(param) if len(candidates) == 0: return False self.configureLine(name=param, code=newvalue, relatedto=best) if str(oldvalue) == 'IGNORE': self.freeRestOfGroup(param) return True elif str(newvalue) == 'IGNORE': # I should check if the group can be ignored # for the time being I just fix all of them to ignore group = int(float(str(self.parameters[param]['group']))) candidates = [] for param in self.parameters.keys(): if group == int(float(str(self.parameters[param]['group']))): candidates.append(param) # print candidates # I should check here if there is any relation to them for param in candidates: self.configureLine(name=param, code=newvalue) return True elif str(newvalue) == 'ADD': group = int(float(str(self.parameters[param]['group']))) if group == 0: # One cannot add a background group return False i = 0 for param in self.parameters: if i <= int(float(str(self.parameters[param]['group']))): i += 1 if (group == 0) and (i == 1): # FIXME: why +1? i += 1 self.addGroup(i, group) return False elif str(newvalue) == 'SHOW': print(self.getEstimationConstraints(param)) return False def addGroup(self, newg, gtype): """Add a fit parameter group with the same fit parameters as an existing group. This function is called when the user selects "ADD" in the "constraints" combobox. :param int newg: New group number :param int gtype: Group number whose parameters we want to copy """ newparam = [] # loop through parameters until we encounter group number `gtype` for param in list(self.parameters): paramgroup = int(float(str(self.parameters[param]['group']))) # copy parameter names in group number `gtype` if paramgroup == gtype: # but replace `gtype` with `newg` newparam.append(param.rstrip("0123456789") + "%d" % newg) xmin = self.parameters[param]['xmin'] xmax = self.parameters[param]['xmax'] # Add new parameters (one table line per parameter) and configureLine each # one by updating xmin and xmax to the same values as group `gtype` line = len(list(self.parameters)) for param in newparam: self.newParameterLine(param, line) line += 1 for param in newparam: self.configureLine(name=param, group=newg, xmin=xmin, xmax=xmax) def freeRestOfGroup(self, workparam): """Set ``code`` to ``"FREE"`` for all fit parameters belonging to the same group as ``workparam``. This is done when the entire group of parameters was previously ignored and one of them has his code set to something different than ``"IGNORE"``. :param workparam: Fit parameter name """ if workparam in self.parameters.keys(): group = int(float(str(self.parameters[workparam]['group']))) for param in self.parameters: if param != workparam and\ group == int(float(str(self.parameters[param]['group']))): self.configureLine(name=param, code='FREE', cons1=0, cons2=0, val1='', val2='') def getRelatedCandidates(self, workparam): """If fit parameter ``workparam`` has a constraint that involves other fit parameters, find possible candidates and try to guess which one is the most likely. :param workparam: Fit parameter name :return: (best_candidate, possible_candidates) tuple :rtype: (str, list[str]) """ candidates = [] for param_name in self.parameters: if param_name != workparam: # ignore parameters that are fixed by a constraint if str(self.parameters[param_name]['code']) not in\ ['IGNORE', 'FACTOR', 'DELTA', 'SUM']: candidates.append(param_name) # take the previous one (before code cell changed) if possible if str(self.parameters[workparam]['relatedto']) in candidates: best = str(self.parameters[workparam]['relatedto']) return best, candidates # take the first with same base name (after removing numbers) for param_name in candidates: basename = param_name.rstrip("0123456789") try: pos = workparam.index(basename) if pos == 0: best = param_name return best, candidates except ValueError: pass # take the first return candidates[0], candidates def setReadOnly(self, parameter, fields): """Make table cells read-only by setting it's flags and omitting flag ``qt.Qt.ItemIsEditable`` :param parameter: Fit parameter names identifying the rows :type parameter: str or list[str] :param fields: Field names identifying the columns :type fields: str or list[str] """ editflags = qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled self.setField(parameter, fields, editflags) def setReadWrite(self, parameter, fields): """Make table cells read-write by setting it's flags including flag ``qt.Qt.ItemIsEditable`` :param parameter: Fit parameter names identifying the rows :type parameter: str or list[str] :param fields: Field names identifying the columns :type fields: str or list[str] """ editflags = qt.Qt.ItemIsSelectable |\ qt.Qt.ItemIsEnabled |\ qt.Qt.ItemIsEditable self.setField(parameter, fields, editflags) def setField(self, parameter, fields, edit_flags): """Set text and flags in a table cell. :param parameter: Fit parameter names identifying the rows :type parameter: str or list[str] :param fields: Field names identifying the columns :type fields: str or list[str] :param edit_flags: Flag combination, e.g:: qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled | qt.Qt.ItemIsEditable """ if isinstance(parameter, list) or \ isinstance(parameter, tuple): paramlist = parameter else: paramlist = [parameter] if isinstance(fields, list) or \ isinstance(fields, tuple): fieldlist = fields else: fieldlist = [fields] # Set _configuring flag to ignore cellChanged signals in # self.onCellChanged _oldvalue = self.__configuring self.__configuring = True # 2D loop through parameter list and field list # to update their cells for param in paramlist: row = list(self.parameters.keys()).index(param) for field in fieldlist: col = self.columnIndexByField(field) if field != 'code': key = field + "_item" item = self.item(row, col) if item is None: item = qt.QTableWidgetItem() item.setText(self.parameters[param][field]) self.setItem(row, col, item) else: item.setText(self.parameters[param][field]) self.parameters[param][key] = item item.setFlags(edit_flags) # Restore previous _configuring flag self.__configuring = _oldvalue def configureLine(self, name, code=None, val1=None, val2=None, sigma=None, estimation=None, fitresult=None, group=None, xmin=None, xmax=None, relatedto=None, cons1=None, cons2=None): """This function updates values in a line of the table :param name: Name of the parameter (serves as unique identifier for a line). :param code: Constraint code *FREE, FIXED, POSITIVE, DELTA, FACTOR, SUM, QUOTED, IGNORE* :param val1: Constraint 1 (can be the index or name of another parameter for code *DELTA, FACTOR, SUM*, or a min value for code *QUOTED*) :param val2: Constraint 2 :param sigma: Standard deviation for a fit parameter :param estimation: Estimated initial value for a fit parameter (used as input to iterative fit) :param fitresult: Final result of fit :param group: Group number of a fit parameter (peak number when doing multi-peak fitting, as each peak corresponds to a group of several consecutive parameters) :param xmin: :param xmax: :param relatedto: Index or name of another fit parameter to which this parameter is related to (constraints) :param cons1: similar meaning to ``val1``, but is always a number :param cons2: similar meaning to ``val2``, but is always a number :return: """ paramlist = list(self.parameters.keys()) if name not in self.parameters: raise KeyError("'%s' is not in the parameter list" % name) # update code first, if specified if code is not None: code = str(code) self.parameters[name]['code'] = code # update combobox index = self.parameters[name]['code_item'].findText(code) self.parameters[name]['code_item'].setCurrentIndex(index) else: # set code to previous value, used later for setting val1 val2 code = self.parameters[name]['code'] # val1 and sigma have special formats if val1 is not None: fmt = None if self.parameters[name]['code'] in\ ['DELTA', 'FACTOR', 'SUM'] else "%8g" self._updateField(name, "val1", val1, fmat=fmt) if sigma is not None: self._updateField(name, "sigma", sigma, fmat="%6.3g") # other fields are formatted as "%8g" keys_params = (("val2", val2), ("estimation", estimation), ("fitresult", fitresult)) for key, value in keys_params: if value is not None: self._updateField(name, key, value, fmat="%8g") # the rest of the parameters are treated as strings and don't need # validation keys_params = (("group", group), ("xmin", xmin), ("xmax", xmax), ("relatedto", relatedto), ("cons1", cons1), ("cons2", cons2)) for key, value in keys_params: if value is not None: self.parameters[name][key] = str(value) # val1 and val2 have different meanings depending on the code if code == 'QUOTED': if val1 is not None: self.parameters[name]['vmin'] = self.parameters[name]['val1'] else: self.parameters[name]['val1'] = self.parameters[name]['vmin'] if val2 is not None: self.parameters[name]['vmax'] = self.parameters[name]['val2'] else: self.parameters[name]['val2'] = self.parameters[name]['vmax'] # cons1 and cons2 are scalar representations of val1 and val2 self.parameters[name]['cons1'] =\ float_else_zero(self.parameters[name]['val1']) self.parameters[name]['cons2'] =\ float_else_zero(self.parameters[name]['val2']) # cons1, cons2 = min(val1, val2), max(val1, val2) if self.parameters[name]['cons1'] > self.parameters[name]['cons2']: self.parameters[name]['cons1'], self.parameters[name]['cons2'] =\ self.parameters[name]['cons2'], self.parameters[name]['cons1'] elif code in ['DELTA', 'SUM', 'FACTOR']: # For these codes, val1 is the fit parameter name on which the # constraint depends if val1 is not None and val1 in paramlist: self.parameters[name]['relatedto'] = self.parameters[name]["val1"] elif val1 is not None: # val1 could be the index of the fit parameter try: self.parameters[name]['relatedto'] = paramlist[int(val1)] except ValueError: self.parameters[name]['relatedto'] = self.parameters[name]["val1"] elif relatedto is not None: # code changed, val1 not specified but relatedto specified: # set val1 to relatedto (pre-fill best guess) self.parameters[name]["val1"] = relatedto # update fields "delta", "sum" or "factor" key = code.lower() self.parameters[name][key] = self.parameters[name]["val2"] # FIXME: val1 is sometimes specified as an index rather than a param name self.parameters[name]['val1'] = self.parameters[name]['relatedto'] # cons1 is the index of the fit parameter in the ordered dictionary if self.parameters[name]['val1'] in paramlist: self.parameters[name]['cons1'] =\ paramlist.index(self.parameters[name]['val1']) # cons2 is the constraint value (factor, delta or sum) try: self.parameters[name]['cons2'] =\ float(str(self.parameters[name]['val2'])) except ValueError: self.parameters[name]['cons2'] = 1.0 if code == "FACTOR" else 0.0 elif code in ['FREE', 'POSITIVE', 'IGNORE', 'FIXED']: self.parameters[name]['val1'] = "" self.parameters[name]['val2'] = "" self.parameters[name]['cons1'] = 0 self.parameters[name]['cons2'] = 0 self._updateCellRWFlags(name, code) def _updateField(self, name, field, value, fmat=None): """Update field in ``self.parameters`` dictionary, if the new value is valid. :param name: Fit parameter name :param field: Field name :param value: New value to assign :type value: String :param fmat: Format string (e.g. "%8g") to be applied if value represents a scalar. If ``None``, format is not modified. If ``value`` is an empty string, ``fmat`` is ignored. """ if value is not None: oldvalue = self.parameters[name][field] if fmat is not None: newvalue = fmat % float(value) if value != "" else "" else: newvalue = value self.parameters[name][field] = newvalue if\ self.validate(name, field, oldvalue, newvalue) else\ oldvalue def _updateCellRWFlags(self, name, code=None): """Set read-only or read-write flags in a row, depending on the constraint code :param name: Fit parameter name identifying the row :param code: Constraint code, in `'FREE', 'POSITIVE', 'IGNORE',` `'FIXED', 'FACTOR', 'DELTA', 'SUM', 'ADD'` :return: """ if code in ['FREE', 'POSITIVE', 'IGNORE', 'FIXED']: self.setReadWrite(name, 'estimation') self.setReadOnly(name, ['fitresult', 'sigma', 'val1', 'val2']) else: self.setReadWrite(name, ['estimation', 'val1', 'val2']) self.setReadOnly(name, ['fitresult', 'sigma']) def getEstimationConstraints(self, param): """ Return tuple ``(estimation, constraints)`` where ``estimation`` is the value in the ``estimate`` field and ``constraints`` are the relevant constraints according to the active code """ estimation = None constraints = None if param in self.parameters.keys(): buf = str(self.parameters[param]['estimation']) if len(buf): estimation = float(buf) else: estimation = 0 if str(self.parameters[param]['code']) in self.code_options: code = self.code_options.index( str(self.parameters[param]['code'])) else: code = str(self.parameters[param]['code']) cons1 = self.parameters[param]['cons1'] cons2 = self.parameters[param]['cons2'] constraints = [code, cons1, cons2] return estimation, constraints def main(args): from silx.math.fit import fittheories from silx.math.fit import fitmanager try: from PyMca5 import PyMcaDataDir except ImportError: raise ImportError("This demo requires PyMca data. Install PyMca5.") import numpy import os app = qt.QApplication(args) tab = Parameters(paramlist=['Height', 'Position', 'FWHM']) tab.showGrid() tab.configureLine(name='Height', estimation='1234', group=0) tab.configureLine(name='Position', code='FIXED', group=1) tab.configureLine(name='FWHM', group=1) y = numpy.loadtxt(os.path.join(PyMcaDataDir.PYMCA_DATA_DIR, "XRFSpectrum.mca")) # FIXME x = numpy.arange(len(y)) * 0.0502883 - 0.492773 fit = fitmanager.FitManager() fit.setdata(x=x, y=y, xmin=20, xmax=150) fit.loadtheories(fittheories) fit.settheory('ahypermet') fit.configure(Yscaling=1., PositiveFwhmFlag=True, PositiveHeightAreaFlag=True, FwhmPoints=16, QuotedPositionFlag=1, HypermetTails=1) fit.setbackground('Linear') fit.estimate() fit.runfit() tab.fillFromFit(fit.fit_results) tab.show() app.exec_() if __name__ == "__main__": main(sys.argv)