Changeset 3b3b40b in sasview for src/sas/qtgui/Perspectives


Ignore:
Timestamp:
Mar 21, 2018 4:17:04 AM (7 years ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
8b480d27
Parents:
e4c475b7
git-author:
Piotr Rozyczko <rozyczko@…> (02/08/18 04:19:04)
git-committer:
Piotr Rozyczko <rozyczko@…> (03/21/18 04:17:04)
Message:

Merging feature branches

Location:
src/sas/qtgui/Perspectives/Fitting
Files:
7 edited

Legend:

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

    re4c475b7 r3b3b40b  
    111111 
    112112        # Disconnect all local slots 
    113         tab_object.disconnect() 
     113        #tab_object.disconnect() 
    114114 
    115115        # Reconnect tab signals to local slots 
     
    178178                            "Not all tabs chosen for fitting have parameters selected for fitting." 
    179179            QtWidgets.QMessageBox.warning(self, 
    180                                            'Warning', 
    181                                             no_params_msg, 
    182                                             QtWidgets.QMessageBox.Ok) 
     180                                          'Warning', 
     181                                           no_params_msg, 
     182                                           QtWidgets.QMessageBox.Ok) 
    183183 
    184184            return 
     
    674674 
    675675        constraint.func = constraint_text 
     676        # param1 is the parameter we're constraining 
    676677        constraint.param = param1 
     678 
    677679        # Find the right tab 
    678680        constrained_tab = self.getObjectByName(model1) 
  • src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py

    r14ec91c5 r3b3b40b  
    88from bumps import fitters 
    99 
     10import sas.qtgui.Utilities.LocalConfig as LocalConfig 
    1011import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary 
    1112 
     
    116117        tab     = FittingWidget(parent=self.parent, data=data, tab_id=self.maxIndex+1) 
    117118        tab.is_batch_fitting = is_batch 
     119 
    118120        # Add this tab to the object library so it can be retrieved by scripting/jupyter 
    119121        tab_name = self.getTabName(is_batch=is_batch) 
     
    123125            self.updateFitDict(data, tab_name) 
    124126        self.maxIndex += 1 
    125         self.addTab(tab, tab_name) 
     127        icon = QtGui.QIcon() 
     128        if is_batch: 
     129            icon.addPixmap(QtGui.QPixmap("src/sas/qtgui/images/icons/layers.svg")) 
     130        self.addTab(tab, icon, tab_name) 
     131        # Show the new tab 
     132        self.setCurrentIndex(self.maxIndex-1) 
     133        # Notify listeners 
    126134        self.tabsModifiedSignal.emit() 
    127135 
     
    140148        ObjectLibrary.addObject(tab_name, tab) 
    141149        self.tabs.append(tab) 
    142         self.addTab(tab, tab_name) 
     150        icon = QtGui.QIcon() 
     151        icon.addPixmap(QtGui.QPixmap("src/sas/qtgui/images/icons/link.svg")) 
     152        self.addTab(tab, icon, tab_name) 
     153 
     154        # This will be the last tab, so set the index accordingly 
     155        self.setCurrentIndex(self.count()-1) 
    143156 
    144157    def updateFitDict(self, item_key, tab_name): 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    re4c475b7 r3b3b40b  
    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    def customModels(self): 
     427        """ Reads in file names in the custom plugin directory """ 
     428        return ModelUtilities._find_models() 
     429 
    417430    def initializeControls(self): 
    418431        """ 
     
    465478        self._poly_model.itemChanged.connect(self.onPolyModelChange) 
    466479        self._magnet_model.itemChanged.connect(self.onMagnetModelChange) 
     480        self.lstParams.selectionModel().selectionChanged.connect(self.onSelectionChanged) 
     481 
     482        # Local signals 
     483        self.batchFittingFinishedSignal.connect(self.batchFitComplete) 
     484        self.fittingFinishedSignal.connect(self.fitComplete) 
    467485 
    468486        # Signals from separate tabs asking for replot 
    469487        self.options_widget.plot_signal.connect(self.onOptionsUpdate) 
     488 
     489        # Signals from other widgets 
     490        self.communicate.customModelDirectoryChanged.connect(self.onCustomModelChange) 
    470491 
    471492    def modelName(self): 
     
    576597        # widget.params[0] is the parameter we're constraining 
    577598        constraint.param = mc_widget.params[0] 
    578         # Function should have the model name preamble 
     599        # parameter should have the model name preamble 
    579600        model_name = self.kernel_module.name 
    580         constraint.func = model_name + "." + c_text 
     601        # param_used is the parameter we're using in constraining function 
     602        param_used = mc_widget.params[1] 
     603        # Replace param_used with model_name.param_used 
     604        updated_param_used = model_name + "." + param_used 
     605        new_func = c_text.replace(param_used, updated_param_used) 
     606        constraint.func = new_func 
    581607        # Which row is the constrained parameter in? 
    582608        row = self.getRowFromName(constraint.param) 
     
    677703        Delete constraints from selected parameters. 
    678704        """ 
    679         self.deleteConstraintOnParameter(param=None) 
     705        params =  [s.data() for s in self.lstParams.selectionModel().selectedRows() 
     706                   if self.isCheckable(s.row())] 
     707        for param in params: 
     708            self.deleteConstraintOnParameter(param=param) 
    680709 
    681710    def deleteConstraintOnParameter(self, param=None): 
     
    686715        max_col = self.lstParams.itemDelegate().param_max 
    687716        for row in range(self._model_model.rowCount()): 
     717            if not self.rowHasConstraint(row): 
     718                continue 
    688719            # Get the Constraint object from of the model item 
    689720            item = self._model_model.item(row, 1) 
    690             if not item.hasChildren(): 
    691                 continue 
    692             constraint = item.child(0).data() 
     721            constraint = self.getConstraintForRow(row) 
    693722            if constraint is None: 
    694723                continue 
     
    816845        return constraints 
    817846 
     847    def getConstraintsForFitting(self): 
     848        """ 
     849        Return a list of constraints in format ready for use in fiting 
     850        """ 
     851        # Get constraints 
     852        constraints = self.getComplexConstraintsForModel() 
     853        # See if there are any constraints across models 
     854        multi_constraints = [cons for cons in constraints if self.isConstraintMultimodel(cons[1])] 
     855 
     856        if multi_constraints: 
     857            # Let users choose what to do 
     858            msg = "The current fit contains constraints relying on other fit pages.\n" 
     859            msg += "Parameters with those constraints are:\n" +\ 
     860                '\n'.join([cons[0] for cons in multi_constraints]) 
     861            msg += "\n\nWould you like to remove these constraints or cancel fitting?" 
     862            msgbox = QtWidgets.QMessageBox(self) 
     863            msgbox.setIcon(QtWidgets.QMessageBox.Warning) 
     864            msgbox.setText(msg) 
     865            msgbox.setWindowTitle("Existing Constraints") 
     866            # custom buttons 
     867            button_remove = QtWidgets.QPushButton("Remove") 
     868            msgbox.addButton(button_remove, QtWidgets.QMessageBox.YesRole) 
     869            button_cancel = QtWidgets.QPushButton("Cancel") 
     870            msgbox.addButton(button_cancel, QtWidgets.QMessageBox.RejectRole) 
     871            retval = msgbox.exec_() 
     872            if retval == QtWidgets.QMessageBox.RejectRole: 
     873                # cancel fit 
     874                raise ValueError("Fitting cancelled") 
     875            else: 
     876                # remove constraint 
     877                for cons in multi_constraints: 
     878                    self.deleteConstraintOnParameter(param=cons[0]) 
     879                # re-read the constraints 
     880                constraints = self.getComplexConstraintsForModel() 
     881 
     882        return constraints 
     883 
    818884    def showModelDescription(self): 
    819885        """ 
     
    874940        self.respondToModelStructure(model=model, structure_factor=structure) 
    875941 
     942    def onCustomModelChange(self): 
     943        """ 
     944        Reload the custom model combobox 
     945        """ 
     946        self.custom_models = self.customModels() 
     947        self.readCustomCategoryInfo() 
     948        # See if we need to update the combo in-place 
     949        if self.cbCategory.currentText() != CATEGORY_CUSTOM: return 
     950 
     951        current_text = self.cbModel.currentText() 
     952        self.cbModel.blockSignals(True) 
     953        self.cbModel.clear() 
     954        self.cbModel.blockSignals(False) 
     955        self.enableModelCombo() 
     956        self.disableStructureCombo() 
     957        # Retrieve the list of models 
     958        model_list = self.master_category_dict[CATEGORY_CUSTOM] 
     959        # Populate the models combobox 
     960        self.cbModel.addItems(sorted([model for (model, _) in model_list])) 
     961        new_index = self.cbModel.findText(current_text) 
     962        if new_index != -1: 
     963            self.cbModel.setCurrentIndex(self.cbModel.findText(current_text)) 
     964 
     965    def onSelectionChanged(self): 
     966        """ 
     967        React to parameter selection 
     968        """ 
     969        rows = self.lstParams.selectionModel().selectedRows() 
     970        # Clean previous messages 
     971        self.communicate.statusBarUpdateSignal.emit("") 
     972        if len(rows) == 1: 
     973            # Show constraint, if present 
     974            row = rows[0].row() 
     975            if self.rowHasConstraint(row): 
     976                func = self.getConstraintForRow(row).func 
     977                if func is not None: 
     978                    self.communicate.statusBarUpdateSignal.emit("Active constrain: "+func) 
     979 
    876980    def replaceConstraintName(self, old_name, new_name=""): 
    877981        """ 
     
    886990                    new_func = func.replace(old_name, new_name) 
    887991                    self._model_model.item(row, 1).child(0).data().func = new_func 
     992 
     993    def isConstraintMultimodel(self, constraint): 
     994        """ 
     995        Check if the constraint function text contains current model name 
     996        """ 
     997        current_model_name = self.kernel_module.name 
     998        if current_model_name in constraint: 
     999            return False 
     1000        else: 
     1001            return True 
    8881002 
    8891003    def updateData(self): 
     
    9371051            self._model_model.clear() 
    9381052            return 
    939  
     1053             
    9401054        # Safely clear and enable the model combo 
    9411055        self.cbModel.blockSignals(True) 
     
    11051219        except ValueError as ex: 
    11061220            # This should not happen! GUI explicitly forbids this situation 
    1107             self.communicate.statusBarUpdateSignal.emit('Fitting attempt without parameters.') 
     1221            self.communicate.statusBarUpdateSignal.emit(str(ex)) 
    11081222            return 
    11091223 
    11101224        # Create the fitting thread, based on the fitter 
    1111         completefn = self.batchFitComplete if self.is_batch_fitting else self.fitComplete 
     1225        completefn = self.batchFittingCompleted if self.is_batch_fitting else self.fittingCompleted 
    11121226 
    11131227        calc_fit = FitThread(handler=handler, 
     
    11461260        pass 
    11471261 
     1262    def batchFittingCompleted(self, result): 
     1263        """ 
     1264        Send the finish message from calculate threads to main thread 
     1265        """ 
     1266        self.batchFittingFinishedSignal.emit(result) 
     1267 
    11481268    def batchFitComplete(self, result): 
    11491269        """ 
     
    11521272        #re-enable the Fit button 
    11531273        self.setFittingStopped() 
     1274        # Show the grid panel 
     1275        self.grid_window = BatchOutputPanel(parent=self, output_data=result[0]) 
     1276        self.grid_window.show() 
     1277 
     1278    def fittingCompleted(self, result): 
     1279        """ 
     1280        Send the finish message from calculate threads to main thread 
     1281        """ 
     1282        self.fittingFinishedSignal.emit(result) 
    11541283 
    11551284    def fitComplete(self, result): 
     
    11621291 
    11631292        if result is None: 
    1164             msg = "Fitting failed after: %s s.\n" % GuiUtils.formatNumber(elapsed) 
     1293            msg = "Fitting failed." 
    11651294            self.communicate.statusBarUpdateSignal.emit(msg) 
    11661295            return 
     
    12281357        smearing, accuracy, smearing_min, smearing_max = self.smearing_widget.state() 
    12291358 
    1230         constraints = self.getComplexConstraintsForModel() 
     1359        constraints = self.getConstraintsForFitting() 
     1360 
    12311361        smearer = None 
    12321362        handler = None 
     
    12421372                             constraints=constraints) 
    12431373            except ValueError as ex: 
    1244                 logging.error("Setting model parameters failed with: %s" % ex) 
    1245                 return 
     1374                raise ValueError("Setting model parameters failed with: %s" % ex) 
    12461375 
    12471376            qmin, qmax, _ = self.logic.computeRangeFromData(data) 
     
    14091538        if not dict: 
    14101539            return 
    1411         if self._model_model.rowCount() == 0: 
     1540        if self._magnet_model.rowCount() == 0: 
    14121541            return 
    14131542 
     
    15551684            self.models[model.name] = model 
    15561685 
     1686        self.readCustomCategoryInfo() 
     1687 
     1688    def readCustomCategoryInfo(self): 
     1689        """ 
     1690        Reads the custom model category 
     1691        """ 
     1692        #Looking for plugins 
     1693        self.plugins = list(self.custom_models.values()) 
     1694        plugin_list = [] 
     1695        for name, plug in self.custom_models.items(): 
     1696            self.models[name] = plug 
     1697            plugin_list.append([name, True]) 
     1698        self.master_category_dict[CATEGORY_CUSTOM] = plugin_list 
     1699 
    15571700    def regenerateModelDict(self): 
    15581701        """ 
     
    16621805        Setting model parameters into QStandardItemModel based on selected _model_ 
    16631806        """ 
    1664         kernel_module = generate.load_kernel_module(model_name) 
     1807        name = model_name 
     1808        if self.cbCategory.currentText() == CATEGORY_CUSTOM: 
     1809            # custom kernel load requires full path 
     1810            name = os.path.join(ModelUtilities.find_plugins_dir(), model_name+".py") 
     1811        kernel_module = generate.load_kernel_module(name) 
    16651812        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', [])) 
    16661813 
  • src/sas/qtgui/Perspectives/Fitting/ModelUtilities.py

    • Property mode changed from 100755 to 100644
    rb3e8629 r3b3b40b  
    179179    plugins = {} 
    180180    for filename in os.listdir(directory): 
     181 
    181182        name, ext = os.path.splitext(filename) 
    182183        if ext == '.py' and not name == '__init__': 
     
    184185            try: 
    185186                model = load_custom_model(path) 
    186                 model.name = PLUGIN_NAME_BASE + model.name 
     187                #model.name = PLUGIN_NAME_BASE + model.name 
    187188                plugins[model.name] = model 
    188189            except Exception: 
     
    415416    implement model 
    416417    """ 
    417     __modelmanager = ModelManagerBase() 
    418     cat_model_list = [__modelmanager.model_dictionary[model_name] for model_name \ 
    419                       in list(__modelmanager.model_dictionary.keys()) \ 
    420                       if model_name not in list(__modelmanager.stored_plugins.keys())] 
    421  
    422     CategoryInstaller.check_install(model_list=cat_model_list) 
     418    def __init__(self): 
     419        self.__modelmanager = ModelManagerBase() 
     420        self.cat_model_list = [self.__modelmanager.model_dictionary[model_name] for model_name \ 
     421                          in list(self.__modelmanager.model_dictionary.keys()) \ 
     422                          if model_name not in list(self.__modelmanager.stored_plugins.keys())] 
     423 
     424        CategoryInstaller.check_install(model_list=self.cat_model_list) 
     425 
    423426    def findModels(self): 
    424427        return self.__modelmanager.findModels() 
  • src/sas/qtgui/Perspectives/Fitting/MultiConstraint.py

    r14ec91c5 r3b3b40b  
    114114 
    115115        # 3. parameter name should be a separate word, but can have "()[]*+-/ " around 
    116         valid_neighbours = "()[]*+-/ " 
    117         start_loc = parameter_string_start -1 
    118         end_loc = parameter_string_end 
    119         if not any([constraint_text[start_loc] == ch for ch in valid_neighbours]): 
    120             return False 
    121         if end_loc < len(constraint_text): 
    122             if not any([constraint_text[end_loc] == ch for ch in valid_neighbours]): 
    123                 return False 
     116        #valid_neighbours = "()[]*+-/ " 
     117        #start_loc = parameter_string_start -1 
     118        #end_loc = parameter_string_end 
     119        #if not any([constraint_text[start_loc] == ch for ch in valid_neighbours]): 
     120        #    return False 
     121        #if end_loc < len(constraint_text): 
     122        #    if not any([constraint_text[end_loc] == ch for ch in valid_neighbours]): 
     123        #        return False 
    124124 
    125125        # 4. replace parameter name with "1" and try to evaluate the expression 
  • src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui

    re4c475b7 r3b3b40b  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>568</width> 
     9    <width>680</width> 
    1010    <height>605</height> 
    1111   </rect> 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/ConstraintWidgetTest.py

    • Property mode changed from 100755 to 100644
    r725d9c06 r3b3b40b  
    2222if not QtWidgets.QApplication.instance(): 
    2323    app = QtWidgets.QApplication(sys.argv) 
    24 #app = QtWidgets.QApplication(sys.argv) 
    2524 
    2625class ConstraintWidgetTest(unittest.TestCase): 
     
    7170        # click on "batch" 
    7271        QTest.mouseClick(self.widget.btnBatch, QtCore.Qt.LeftButton) 
    73         app.processEvents() 
     72        QtWidgets.QApplication.processEvents() 
    7473        # See what the current type is now 
    7574        self.assertEqual(self.widget.currentType, "BatchPage") 
     
    7877        # Go back to single fit 
    7978        QTest.mouseClick(self.widget.btnSingle, QtCore.Qt.LeftButton) 
    80         app.processEvents() 
     79        QtWidgets.QApplication.processEvents() 
    8180        # See what the current type is now 
    8281        self.assertEqual(self.widget.currentType, "FitPage") 
Note: See TracChangeset for help on using the changeset viewer.