Ignore:
Timestamp:
Sep 21, 2018 8:34:54 AM (6 years ago)
Author:
Torin Cooper-Bennun <torin.cooper-bennun@…>
Branches:
ESS_GUI, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
80a327d
Parents:
6fbb859 (diff), dad086f (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'ESS_GUI' into ESS_GUI_iss1052

File:
1 edited

Legend:

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

    r6fbb859 radf1c2a  
    2828from sas.qtgui.Plotting.PlotterData import Data1D 
    2929from sas.qtgui.Plotting.PlotterData import Data2D 
     30from sas.qtgui.Plotting.Plotter import PlotterWidget 
    3031 
    3132from sas.qtgui.Perspectives.Fitting.UI.FittingWidgetUI import Ui_FittingWidgetUI 
     
    5758DEFAULT_POLYDISP_FUNCTION = 'gaussian' 
    5859 
     60# CRUFT: remove when new release of sasmodels is available 
     61# https://github.com/SasView/sasview/pull/181#discussion_r218135162 
     62from sasmodels.sasview_model import SasviewModel 
     63if not hasattr(SasviewModel, 'get_weights'): 
     64    def get_weights(self, name): 
     65        """ 
     66        Returns the polydispersity distribution for parameter *name* as *value* and *weight* arrays. 
     67        """ 
     68        # type: (str) -> Tuple(np.ndarray, np.ndarray) 
     69        _, x, w = self._get_weights(self._model_info.parameters[name]) 
     70        return x, w 
     71 
     72    SasviewModel.get_weights = get_weights 
    5973 
    6074logger = logging.getLogger(__name__) 
     
    273287        self.theory_item = None 
    274288 
     289        # list column widths 
     290        self.lstParamHeaderSizes = {} 
     291 
    275292        # signal communicator 
    276293        self.communicate = self.parent.communicate 
     
    361378        self.lstParams.customContextMenuRequested.connect(self.showModelContextMenu) 
    362379        self.lstParams.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False) 
     380        # Column resize signals 
     381        self.lstParams.header().sectionResized.connect(self.onColumnWidthUpdate) 
     382 
    363383        # Poly model displayed in poly list 
    364384        self.lstPoly.setModel(self._poly_model) 
     
    642662        # Create and display the widget for param1 and param2 
    643663        mc_widget = MultiConstraint(self, params=params_list) 
     664        # Check if any of the parameters are polydisperse 
     665        if not np.any([FittingUtilities.isParamPolydisperse(p, self.model_parameters, is2D=self.is2D) for p in params_list]): 
     666            # no parameters are pd - reset the text to not show the warning 
     667            mc_widget.lblWarning.setText("") 
    644668        if mc_widget.exec_() != QtWidgets.QDialog.Accepted: 
    645669            return 
     
    10611085            if not self.rowHasConstraint(row): 
    10621086                return 
     1087            constr = self.getConstraintForRow(row) 
    10631088            func = self.getConstraintForRow(row).func 
    1064             if func is not None: 
    1065                 self.communicate.statusBarUpdateSignal.emit("Active constrain: "+func) 
     1089            if constr.func is not None: 
     1090                # inter-parameter constraint 
     1091                update_text = "Active constraint: "+func 
     1092            elif constr.param == rows[0].data(): 
     1093                # current value constraint 
     1094                update_text = "Value constrained to: " + str(constr.value) 
     1095            else: 
     1096                # ill defined constraint 
     1097                return 
     1098            self.communicate.statusBarUpdateSignal.emit(update_text) 
    10661099 
    10671100    def replaceConstraintName(self, old_name, new_name=""): 
     
    11001133            # Create default datasets if no data passed 
    11011134            self.createDefaultDataset() 
     1135            self.theory_item = None # ensure theory is recalc. before plot, see showTheoryPlot() 
    11021136 
    11031137    def respondToModelStructure(self, model=None, structure_factor=None): 
     
    11071141        # kernel parameters -> model_model 
    11081142        self.SASModelToQModel(model, structure_factor) 
     1143 
     1144        for column, width in self.lstParamHeaderSizes.items(): 
     1145            self.lstParams.setColumnWidth(column, width) 
    11091146 
    11101147        # Update plot 
     
    12191256        if model_column in [delegate.poly_pd, delegate.poly_error, delegate.poly_min, delegate.poly_max]: 
    12201257            row = self.getRowFromName(parameter_name) 
    1221             param_item = self._model_model.item(row) 
     1258            param_item = self._model_model.item(row).child(0).child(0, model_column) 
     1259            if param_item is None: 
     1260                return 
    12221261            self._model_model.blockSignals(True) 
    1223             param_item.child(0).child(0, model_column).setText(item.text()) 
     1262            param_item.setText(item.text()) 
    12241263            self._model_model.blockSignals(False) 
    12251264 
     
    13661405        self.communicate.statusBarUpdateSignal.emit('Fitting started...') 
    13671406        self.fit_started = True 
     1407 
    13681408        # Disable some elements 
    1369         self.setFittingStarted() 
     1409        self.disableInteractiveElements() 
    13701410 
    13711411    def stopFit(self): 
     
    13761416            return 
    13771417        self.calc_fit.stop() 
    1378         #self.fit_started=False 
    13791418        #re-enable the Fit button 
    1380         self.setFittingStopped() 
     1419        self.enableInteractiveElements() 
    13811420 
    13821421        msg = "Fitting cancelled." 
     
    13921431        """ 
    13931432        """ 
    1394         self.setFittingStopped() 
     1433        self.enableInteractiveElements() 
    13951434        msg = "Fitting failed with: "+ str(reason) 
    13961435        self.communicate.statusBarUpdateSignal.emit(msg) 
     
    14091448        """ 
    14101449        #re-enable the Fit button 
    1411         self.setFittingStopped() 
     1450        self.enableInteractiveElements() 
    14121451 
    14131452        if len(result) == 0: 
     
    14811520        """ 
    14821521        #re-enable the Fit button 
    1483         self.setFittingStopped() 
     1522        self.enableInteractiveElements() 
    14841523 
    14851524        if len(result) == 0: 
     
    16661705        self.iterateOverModel(updatePolyValues) 
    16671706        self._model_model.itemChanged.connect(self.onMainParamsChange) 
    1668  
    1669         # Adjust the table cells width. 
    1670         # TODO: find a way to dynamically adjust column width while resized expanding 
    1671         self.lstParams.resizeColumnToContents(0) 
    1672         self.lstParams.resizeColumnToContents(4) 
    1673         self.lstParams.resizeColumnToContents(5) 
    1674         self.lstParams.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) 
    16751707 
    16761708    def iterateOverPolyModel(self, func): 
     
    18131845        self.cmdPlot.setText("Show Plot") 
    18141846        # Force data recalculation so existing charts are updated 
    1815         self.showPlot() 
     1847        if not self.data_is_loaded: 
     1848            self.showTheoryPlot() 
     1849        else: 
     1850            self.showPlot() 
    18161851        # This is an important processEvent. 
    18171852        # This allows charts to be properly updated in order 
    18181853        # of plots being applied. 
    18191854        QtWidgets.QApplication.processEvents() 
    1820         self.recalculatePlotData() 
     1855        self.recalculatePlotData() # recalc+plot theory again (2nd) 
    18211856 
    18221857    def onSmearingOptionsUpdate(self): 
     
    18341869        self.calculateQGridForModel() 
    18351870 
     1871    def showTheoryPlot(self): 
     1872        """ 
     1873        Show the current theory plot in MPL 
     1874        """ 
     1875        # Show the chart if ready 
     1876        if self.theory_item is None: 
     1877            self.recalculatePlotData() 
     1878        elif self.model_data: 
     1879            self._requestPlots(self.model_data.filename, self.theory_item.model()) 
     1880 
    18361881    def showPlot(self): 
    18371882        """ 
     
    18391884        """ 
    18401885        # Show the chart if ready 
    1841         data_to_show = self.data if self.data_is_loaded else self.model_data 
    1842         if data_to_show is not None: 
    1843             self.communicate.plotRequestedSignal.emit([data_to_show], self.tab_id) 
     1886        data_to_show = self.data 
     1887        # Any models for this page 
     1888        current_index = self.all_data[self.data_index] 
     1889        item = self._requestPlots(self.data.filename, current_index.model()) 
     1890        if item: 
     1891            # fit+data has not been shown - show just data 
     1892            self.communicate.plotRequestedSignal.emit([item, data_to_show], self.tab_id) 
     1893 
     1894    def _requestPlots(self, item_name, item_model): 
     1895        """ 
     1896        Emits plotRequestedSignal for all plots found in the given model under the provided item name. 
     1897        """ 
     1898        fitpage_name = "" if self.tab_id is None else "M"+str(self.tab_id) 
     1899        plots = GuiUtils.plotsFromFilename(item_name, item_model) 
     1900        # Has the fitted data been shown? 
     1901        data_shown = False 
     1902        item = None 
     1903        for item, plot in plots.items(): 
     1904            if fitpage_name in plot.name: 
     1905                data_shown = True 
     1906                self.communicate.plotRequestedSignal.emit([item, plot], self.tab_id) 
     1907        # return the last data item seen, if nothing was plotted; supposed to be just data) 
     1908        return None if data_shown else item 
    18441909 
    18451910    def onOptionsUpdate(self): 
     
    20132078            self.setMagneticModel() 
    20142079 
    2015         # Adjust the table cells width 
    2016         self.lstParams.resizeColumnToContents(0) 
    2017         self.lstParams.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) 
    2018  
    20192080        # Now we claim the model has been loaded 
    20202081        self.model_is_loaded = True 
     
    20752136        self.kernel_module = self.models[model_name]() 
    20762137 
     2138        # Change the model name to a monicker 
     2139        self.kernel_module.name = self.modelName() 
     2140 
    20772141        # Explicitly add scale and background with default values 
    20782142        temp_undo_state = self.undo_supported 
     
    21072171            # Structure factor is the only selected model; build it and show all its params 
    21082172            self.kernel_module = self.models[structure_factor]() 
     2173            self.kernel_module.name = self.modelName() 
    21092174            s_params = self.kernel_module._model_info.parameters 
    21102175            s_params_orig = s_params 
     
    21172182 
    21182183            self.kernel_module = MultiplicationModel(p_kernel, s_kernel) 
     2184            # Modify the name to correspond to shown items 
     2185            self.kernel_module.name = self.modelName() 
    21192186            all_params = self.kernel_module._model_info.parameters.kernel_parameters 
    21202187            all_param_names = [param.name for param in all_params] 
     
    24042471 
    24052472        # add polydisperse parameters if asked 
    2406         if self.chkPolydispersity.isChecked(): 
     2473        if self.chkPolydispersity.isChecked() and self._poly_model.rowCount() > 0: 
    24072474            for key, value in self.poly_params.items(): 
    24082475                model.setParam(key, value) 
    24092476        # add magnetic params if asked 
    24102477        if self.chkMagnetism.isChecked(): 
    2411             for key, value in self.magnet_params.items(): 
     2478            for key, value in self.magnet_params.items() and self._magnet_model.rowCount() > 0: 
    24122479                model.setParam(key, value) 
    24132480 
     
    24272494        weight = FittingUtilities.getWeight(data=data, is2d=self.is2D, flag=self.weighting) 
    24282495 
     2496        # Disable buttons/table 
     2497        self.disableInteractiveElements() 
    24292498        # Awful API to a backend method. 
    24302499        calc_thread = self.methodCalculateForData()(data=data, 
     
    24682537        Thread returned error 
    24692538        """ 
     2539        # Bring the GUI to normal state 
     2540        self.enableInteractiveElements() 
    24702541        print("Calculate Data failed with ", reason) 
    24712542 
     
    24802551        Plot the current 1D data 
    24812552        """ 
     2553        # Bring the GUI to normal state 
     2554        self.enableInteractiveElements() 
     2555        if return_data is None: 
     2556            self.calculateDataFailed("Results not available.") 
     2557            return 
    24822558        fitted_data = self.logic.new1DPlot(return_data, self.tab_id) 
     2559 
    24832560        residuals = self.calculateResiduals(fitted_data) 
    24842561        self.model_data = fitted_data 
     
    24882565 
    24892566        if self.data_is_loaded: 
    2490             # delete any plots associated with the data that were not updated (e.g. to remove beta(Q), S_eff(Q)) 
     2567            # delete any plots associated with the data that were not updated 
     2568            # (e.g. to remove beta(Q), S_eff(Q)) 
    24912569            GuiUtils.deleteRedundantPlots(self.all_data[self.data_index], new_plots) 
    24922570            pass 
    24932571        else: 
    2494             # delete theory items for the model, in order to get rid of any redundant items, e.g. beta(Q), S_eff(Q) 
     2572            # delete theory items for the model, in order to get rid of any 
     2573            # redundant items, e.g. beta(Q), S_eff(Q) 
    24952574            self.communicate.deleteIntermediateTheoryPlotsSignal.emit(self.kernel_module.id) 
     2575 
     2576        # Create plots for parameters with enabled polydispersity 
     2577        for plot in FittingUtilities.plotPolydispersities(return_data.get('model', None)): 
     2578            data_id = fitted_data.id.split() 
     2579            plot.id = "{} [{}] {}".format(data_id[0], plot.name, " ".join(data_id[1:])) 
     2580            data_name = fitted_data.name.split() 
     2581            plot.name = " ".join([data_name[0], plot.name] + data_name[1:]) 
     2582            self.createNewIndex(plot) 
     2583            new_plots.append(plot) 
    24962584 
    24972585        # Create plots for intermediate product data 
     
    25402628        Plot the current 2D data 
    25412629        """ 
     2630        # Bring the GUI to normal state 
     2631        self.enableInteractiveElements() 
     2632 
    25422633        fitted_data = self.logic.new2DPlot(return_data) 
    25432634        residuals = self.calculateResiduals(fitted_data) 
     
    25742665        residuals_plot = FittingUtilities.plotResiduals(self.data, weighted_data) 
    25752666        residuals_plot.id = "Residual " + residuals_plot.id 
     2667        residuals_plot.plot_role = Data1D.ROLE_RESIDUAL 
    25762668        self.createNewIndex(residuals_plot) 
    25772669        return residuals_plot 
     
    26092701        Thread threw an exception. 
    26102702        """ 
     2703        # Bring the GUI to normal state 
     2704        self.enableInteractiveElements() 
    26112705        # TODO: remimplement thread cancellation 
    26122706        logger.error("".join(traceback.format_exception(etype, value, tb))) 
     
    28202914        self._poly_model.setData(fname_index, fname) 
    28212915 
     2916    def onColumnWidthUpdate(self, index, old_size, new_size): 
     2917        """ 
     2918        Simple state update of the current column widths in the  param list 
     2919        """ 
     2920        self.lstParamHeaderSizes[index] = new_size 
     2921 
    28222922    def setMagneticModel(self): 
    28232923        """ 
     
    28912991 
    28922992        func = QtWidgets.QComboBox() 
    2893         # Available range of shells displayed in the combobox 
    2894         func.addItems([str(i) for i in range(param_length+1)]) 
    2895  
    2896         # Respond to index change 
    2897         func.currentIndexChanged.connect(self.modifyShellsInList) 
    28982993 
    28992994        # cell 2: combobox 
    29002995        item2 = QtGui.QStandardItem() 
    2901         self._model_model.appendRow([item1, item2]) 
     2996 
     2997        # cell 3: min value 
     2998        item3 = QtGui.QStandardItem() 
     2999 
     3000        # cell 4: max value 
     3001        item4 = QtGui.QStandardItem() 
     3002 
     3003        # cell 4: SLD button 
     3004        item5 = QtGui.QStandardItem() 
     3005        button = QtWidgets.QPushButton() 
     3006        button.setText("Show SLD Profile") 
     3007 
     3008        self._model_model.appendRow([item1, item2, item3, item4, item5]) 
    29023009 
    29033010        # Beautify the row:  span columns 2-4 
    29043011        shell_row = self._model_model.rowCount() 
    29053012        shell_index = self._model_model.index(shell_row-1, 1) 
     3013        button_index = self._model_model.index(shell_row-1, 4) 
    29063014 
    29073015        self.lstParams.setIndexWidget(shell_index, func) 
     3016        self.lstParams.setIndexWidget(button_index, button) 
    29083017        self._n_shells_row = shell_row - 1 
    29093018 
     
    29183027            logger.error("Could not find %s in kernel parameters.", param_name) 
    29193028        default_shell_count = shell_par.default 
     3029        shell_min = 0 
     3030        shell_max = 0 
     3031        try: 
     3032            shell_min = int(shell_par.limits[0]) 
     3033            shell_max = int(shell_par.limits[1]) 
     3034        except IndexError as ex: 
     3035            # no info about limits 
     3036            pass 
     3037        item3.setText(str(shell_min)) 
     3038        item4.setText(str(shell_max)) 
     3039 
     3040        # Respond to index change 
     3041        func.currentTextChanged.connect(self.modifyShellsInList) 
     3042 
     3043        # Respond to button press 
     3044        button.clicked.connect(self.onShowSLDProfile) 
     3045 
     3046        # Available range of shells displayed in the combobox 
     3047        func.addItems([str(i) for i in range(shell_min, shell_max+1)]) 
    29203048 
    29213049        # Add default number of shells to the model 
    2922         func.setCurrentIndex(default_shell_count) 
    2923  
    2924     def modifyShellsInList(self, index): 
     3050        func.setCurrentText(str(default_shell_count)) 
     3051 
     3052    def modifyShellsInList(self, text): 
    29253053        """ 
    29263054        Add/remove additional multishell parameters 
     
    29293057        first_row = self._n_shells_row + 1 
    29303058        remove_rows = self._num_shell_params 
    2931  
     3059        try: 
     3060            index = int(text) 
     3061        except ValueError: 
     3062            # bad text on the control! 
     3063            index = 0 
     3064            logger.error("Multiplicity incorrect! Setting to 0") 
     3065        self.kernel_module.multiplicity = index 
    29323066        if remove_rows > 1: 
    29333067            self._model_model.removeRows(first_row, remove_rows) 
     
    29563090        self.setMagneticModel() 
    29573091 
    2958     def setFittingStarted(self): 
    2959         """ 
    2960         Set buttion caption on fitting start 
     3092    def onShowSLDProfile(self): 
     3093        """ 
     3094        Show a quick plot of SLD profile 
     3095        """ 
     3096        # get profile data 
     3097        x, y = self.kernel_module.getProfile() 
     3098        y *= 1.0e6 
     3099        profile_data = Data1D(x=x, y=y) 
     3100        profile_data.name = "SLD" 
     3101        profile_data.scale = 'linear' 
     3102        profile_data.symbol = 'Line' 
     3103        profile_data.hide_error = True 
     3104        profile_data._xaxis = "R(\AA)" 
     3105        profile_data._yaxis = "SLD(10^{-6}\AA^{-2})" 
     3106 
     3107        plotter = PlotterWidget(self, quickplot=True) 
     3108        plotter.data = profile_data 
     3109        plotter.showLegend = True 
     3110        plotter.plot(hide_error=True, marker='-') 
     3111 
     3112        self.plot_widget = QtWidgets.QWidget() 
     3113        self.plot_widget.setWindowTitle("Scattering Length Density Profile") 
     3114        layout = QtWidgets.QVBoxLayout() 
     3115        layout.addWidget(plotter) 
     3116        self.plot_widget.setLayout(layout) 
     3117        self.plot_widget.show() 
     3118 
     3119    def setInteractiveElements(self, enabled=True): 
     3120        """ 
     3121        Switch interactive GUI elements on/off 
     3122        """ 
     3123        assert isinstance(enabled, bool) 
     3124 
     3125        self.lstParams.setEnabled(enabled) 
     3126        self.lstPoly.setEnabled(enabled) 
     3127        self.lstMagnetic.setEnabled(enabled) 
     3128 
     3129        self.cbCategory.setEnabled(enabled) 
     3130        self.cbModel.setEnabled(enabled) 
     3131        self.cbStructureFactor.setEnabled(enabled) 
     3132 
     3133        self.chkPolydispersity.setEnabled(enabled) 
     3134        self.chkMagnetism.setEnabled(enabled) 
     3135        self.chk2DView.setEnabled(enabled) 
     3136 
     3137    def enableInteractiveElements(self): 
     3138        """ 
     3139        Set buttion caption on fitting/calculate finish 
     3140        Enable the param table(s) 
     3141        """ 
     3142        # Notify the user that fitting is available 
     3143        self.cmdFit.setStyleSheet('QPushButton {color: black;}') 
     3144        self.cmdFit.setText("Fit") 
     3145        self.fit_started = False 
     3146        self.setInteractiveElements(True) 
     3147 
     3148    def disableInteractiveElements(self): 
     3149        """ 
     3150        Set buttion caption on fitting/calculate start 
     3151        Disable the param table(s) 
    29613152        """ 
    29623153        # Notify the user that fitting is being run 
     
    29643155        self.cmdFit.setStyleSheet('QPushButton {color: red;}') 
    29653156        self.cmdFit.setText('Stop fit') 
    2966  
    2967     def setFittingStopped(self): 
    2968         """ 
    2969         Set button caption on fitting stop 
    2970         """ 
    2971         # Notify the user that fitting is available 
    2972         self.cmdFit.setStyleSheet('QPushButton {color: black;}') 
    2973         self.cmdFit.setText("Fit") 
    2974         self.fit_started = False 
     3157        self.setInteractiveElements(False) 
    29753158 
    29763159    def readFitPage(self, fp): 
Note: See TracChangeset for help on using the changeset viewer.