Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    re4c475b7 r8b480d27  
    2424import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    2525import sas.qtgui.Utilities.LocalConfig as LocalConfig 
     26from sas.qtgui.Utilities.GridPanel import BatchOutputPanel 
    2627from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 
    2728from sas.qtgui.Plotting.PlotterData import Data1D 
     
    3637from sas.qtgui.Perspectives.Fitting.FittingLogic import FittingLogic 
    3738from sas.qtgui.Perspectives.Fitting import FittingUtilities 
     39from sas.qtgui.Perspectives.Fitting import ModelUtilities 
    3840from sas.qtgui.Perspectives.Fitting.SmearingWidget import SmearingWidget 
    3941from sas.qtgui.Perspectives.Fitting.OptionsWidget import OptionsWidget 
     
    5052CATEGORY_DEFAULT = "Choose category..." 
    5153CATEGORY_STRUCTURE = "Structure Factor" 
     54CATEGORY_CUSTOM = "Plugin Models" 
    5255STRUCTURE_DEFAULT = "None" 
    5356 
     
    8386    constraintAddedSignal = QtCore.pyqtSignal(list) 
    8487    newModelSignal = QtCore.pyqtSignal() 
     88    fittingFinishedSignal = QtCore.pyqtSignal(tuple) 
     89    batchFittingFinishedSignal = QtCore.pyqtSignal(tuple) 
     90 
    8591    def __init__(self, parent=None, data=None, tab_id=1): 
    8692 
     
    211217        self.page_stack = [] 
    212218        self.all_data = [] 
     219        # custom plugin models 
     220        # {model.name:model} 
     221        self.custom_models = self.customModels() 
    213222        # Polydisp widget table default index for function combobox 
    214223        self.orig_poly_index = 3 
     
    415424            self.onSelectModel() 
    416425 
     426    @classmethod 
     427    def customModels(cls): 
     428        """ Reads in file names in the custom plugin directory """ 
     429        return ModelUtilities._find_models() 
     430 
    417431    def initializeControls(self): 
    418432        """ 
     
    465479        self._poly_model.itemChanged.connect(self.onPolyModelChange) 
    466480        self._magnet_model.itemChanged.connect(self.onMagnetModelChange) 
     481        self.lstParams.selectionModel().selectionChanged.connect(self.onSelectionChanged) 
     482 
     483        # Local signals 
     484        self.batchFittingFinishedSignal.connect(self.batchFitComplete) 
     485        self.fittingFinishedSignal.connect(self.fitComplete) 
    467486 
    468487        # Signals from separate tabs asking for replot 
    469488        self.options_widget.plot_signal.connect(self.onOptionsUpdate) 
     489 
     490        # Signals from other widgets 
     491        self.communicate.customModelDirectoryChanged.connect(self.onCustomModelChange) 
    470492 
    471493    def modelName(self): 
     
    576598        # widget.params[0] is the parameter we're constraining 
    577599        constraint.param = mc_widget.params[0] 
    578         # Function should have the model name preamble 
     600        # parameter should have the model name preamble 
    579601        model_name = self.kernel_module.name 
    580         constraint.func = model_name + "." + c_text 
     602        # param_used is the parameter we're using in constraining function 
     603        param_used = mc_widget.params[1] 
     604        # Replace param_used with model_name.param_used 
     605        updated_param_used = model_name + "." + param_used 
     606        new_func = c_text.replace(param_used, updated_param_used) 
     607        constraint.func = new_func 
    581608        # Which row is the constrained parameter in? 
    582609        row = self.getRowFromName(constraint.param) 
     
    677704        Delete constraints from selected parameters. 
    678705        """ 
    679         self.deleteConstraintOnParameter(param=None) 
     706        params =  [s.data() for s in self.lstParams.selectionModel().selectedRows() 
     707                   if self.isCheckable(s.row())] 
     708        for param in params: 
     709            self.deleteConstraintOnParameter(param=param) 
    680710 
    681711    def deleteConstraintOnParameter(self, param=None): 
     
    686716        max_col = self.lstParams.itemDelegate().param_max 
    687717        for row in range(self._model_model.rowCount()): 
     718            if not self.rowHasConstraint(row): 
     719                continue 
    688720            # Get the Constraint object from of the model item 
    689721            item = self._model_model.item(row, 1) 
    690             if not item.hasChildren(): 
    691                 continue 
    692             constraint = item.child(0).data() 
     722            constraint = self.getConstraintForRow(row) 
    693723            if constraint is None: 
    694724                continue 
     
    816846        return constraints 
    817847 
     848    def getConstraintsForFitting(self): 
     849        """ 
     850        Return a list of constraints in format ready for use in fiting 
     851        """ 
     852        # Get constraints 
     853        constraints = self.getComplexConstraintsForModel() 
     854        # See if there are any constraints across models 
     855        multi_constraints = [cons for cons in constraints if self.isConstraintMultimodel(cons[1])] 
     856 
     857        if multi_constraints: 
     858            # Let users choose what to do 
     859            msg = "The current fit contains constraints relying on other fit pages.\n" 
     860            msg += "Parameters with those constraints are:\n" +\ 
     861                '\n'.join([cons[0] for cons in multi_constraints]) 
     862            msg += "\n\nWould you like to remove these constraints or cancel fitting?" 
     863            msgbox = QtWidgets.QMessageBox(self) 
     864            msgbox.setIcon(QtWidgets.QMessageBox.Warning) 
     865            msgbox.setText(msg) 
     866            msgbox.setWindowTitle("Existing Constraints") 
     867            # custom buttons 
     868            button_remove = QtWidgets.QPushButton("Remove") 
     869            msgbox.addButton(button_remove, QtWidgets.QMessageBox.YesRole) 
     870            button_cancel = QtWidgets.QPushButton("Cancel") 
     871            msgbox.addButton(button_cancel, QtWidgets.QMessageBox.RejectRole) 
     872            retval = msgbox.exec_() 
     873            if retval == QtWidgets.QMessageBox.RejectRole: 
     874                # cancel fit 
     875                raise ValueError("Fitting cancelled") 
     876            else: 
     877                # remove constraint 
     878                for cons in multi_constraints: 
     879                    self.deleteConstraintOnParameter(param=cons[0]) 
     880                # re-read the constraints 
     881                constraints = self.getComplexConstraintsForModel() 
     882 
     883        return constraints 
     884 
    818885    def showModelDescription(self): 
    819886        """ 
     
    874941        self.respondToModelStructure(model=model, structure_factor=structure) 
    875942 
     943    def onCustomModelChange(self): 
     944        """ 
     945        Reload the custom model combobox 
     946        """ 
     947        self.custom_models = self.customModels() 
     948        self.readCustomCategoryInfo() 
     949        # See if we need to update the combo in-place 
     950        if self.cbCategory.currentText() != CATEGORY_CUSTOM: return 
     951 
     952        current_text = self.cbModel.currentText() 
     953        self.cbModel.blockSignals(True) 
     954        self.cbModel.clear() 
     955        self.cbModel.blockSignals(False) 
     956        self.enableModelCombo() 
     957        self.disableStructureCombo() 
     958        # Retrieve the list of models 
     959        model_list = self.master_category_dict[CATEGORY_CUSTOM] 
     960        # Populate the models combobox 
     961        self.cbModel.addItems(sorted([model for (model, _) in model_list])) 
     962        new_index = self.cbModel.findText(current_text) 
     963        if new_index != -1: 
     964            self.cbModel.setCurrentIndex(self.cbModel.findText(current_text)) 
     965 
     966    def onSelectionChanged(self): 
     967        """ 
     968        React to parameter selection 
     969        """ 
     970        rows = self.lstParams.selectionModel().selectedRows() 
     971        # Clean previous messages 
     972        self.communicate.statusBarUpdateSignal.emit("") 
     973        if len(rows) == 1: 
     974            # Show constraint, if present 
     975            row = rows[0].row() 
     976            if self.rowHasConstraint(row): 
     977                func = self.getConstraintForRow(row).func 
     978                if func is not None: 
     979                    self.communicate.statusBarUpdateSignal.emit("Active constrain: "+func) 
     980 
    876981    def replaceConstraintName(self, old_name, new_name=""): 
    877982        """ 
     
    886991                    new_func = func.replace(old_name, new_name) 
    887992                    self._model_model.item(row, 1).child(0).data().func = new_func 
     993 
     994    def isConstraintMultimodel(self, constraint): 
     995        """ 
     996        Check if the constraint function text contains current model name 
     997        """ 
     998        current_model_name = self.kernel_module.name 
     999        if current_model_name in constraint: 
     1000            return False 
     1001        else: 
     1002            return True 
    8881003 
    8891004    def updateData(self): 
     
    11051220        except ValueError as ex: 
    11061221            # This should not happen! GUI explicitly forbids this situation 
    1107             self.communicate.statusBarUpdateSignal.emit('Fitting attempt without parameters.') 
     1222            self.communicate.statusBarUpdateSignal.emit(str(ex)) 
    11081223            return 
    11091224 
    11101225        # Create the fitting thread, based on the fitter 
    1111         completefn = self.batchFitComplete if self.is_batch_fitting else self.fitComplete 
     1226        completefn = self.batchFittingCompleted if self.is_batch_fitting else self.fittingCompleted 
    11121227 
    11131228        calc_fit = FitThread(handler=handler, 
     
    11461261        pass 
    11471262 
     1263    def batchFittingCompleted(self, result): 
     1264        """ 
     1265        Send the finish message from calculate threads to main thread 
     1266        """ 
     1267        self.batchFittingFinishedSignal.emit(result) 
     1268 
    11481269    def batchFitComplete(self, result): 
    11491270        """ 
     
    11521273        #re-enable the Fit button 
    11531274        self.setFittingStopped() 
     1275        # Show the grid panel 
     1276        self.grid_window = BatchOutputPanel(parent=self, output_data=result[0]) 
     1277        self.grid_window.show() 
     1278 
     1279    def fittingCompleted(self, result): 
     1280        """ 
     1281        Send the finish message from calculate threads to main thread 
     1282        """ 
     1283        self.fittingFinishedSignal.emit(result) 
    11541284 
    11551285    def fitComplete(self, result): 
     
    11621292 
    11631293        if result is None: 
    1164             msg = "Fitting failed after: %s s.\n" % GuiUtils.formatNumber(elapsed) 
     1294            msg = "Fitting failed." 
    11651295            self.communicate.statusBarUpdateSignal.emit(msg) 
    11661296            return 
     
    12281358        smearing, accuracy, smearing_min, smearing_max = self.smearing_widget.state() 
    12291359 
     1360        # Get the constraints. 
    12301361        constraints = self.getComplexConstraintsForModel() 
     1362        if fitter is None: 
     1363            # For single fits - check for inter-model constraints 
     1364            constraints = self.getConstraintsForFitting() 
     1365 
    12311366        smearer = None 
    12321367        handler = None 
     
    12421377                             constraints=constraints) 
    12431378            except ValueError as ex: 
    1244                 logging.error("Setting model parameters failed with: %s" % ex) 
    1245                 return 
     1379                raise ValueError("Setting model parameters failed with: %s" % ex) 
    12461380 
    12471381            qmin, qmax, _ = self.logic.computeRangeFromData(data) 
     
    14091543        if not dict: 
    14101544            return 
    1411         if self._model_model.rowCount() == 0: 
     1545        if self._magnet_model.rowCount() == 0: 
    14121546            return 
    14131547 
     
    15551689            self.models[model.name] = model 
    15561690 
     1691        self.readCustomCategoryInfo() 
     1692 
     1693    def readCustomCategoryInfo(self): 
     1694        """ 
     1695        Reads the custom model category 
     1696        """ 
     1697        #Looking for plugins 
     1698        self.plugins = list(self.custom_models.values()) 
     1699        plugin_list = [] 
     1700        for name, plug in self.custom_models.items(): 
     1701            self.models[name] = plug 
     1702            plugin_list.append([name, True]) 
     1703        self.master_category_dict[CATEGORY_CUSTOM] = plugin_list 
     1704 
    15571705    def regenerateModelDict(self): 
    15581706        """ 
     
    16621810        Setting model parameters into QStandardItemModel based on selected _model_ 
    16631811        """ 
    1664         kernel_module = generate.load_kernel_module(model_name) 
     1812        name = model_name 
     1813        if self.cbCategory.currentText() == CATEGORY_CUSTOM: 
     1814            # custom kernel load requires full path 
     1815            name = os.path.join(ModelUtilities.find_plugins_dir(), model_name+".py") 
     1816        kernel_module = generate.load_kernel_module(name) 
    16651817        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', [])) 
    16661818 
Note: See TracChangeset for help on using the changeset viewer.