Changeset adf1c2a in sasview for src/sas


Ignore:
Timestamp:
Sep 21, 2018 10: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

Location:
src/sas
Files:
35 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/Calculators/UnitTesting/SLDCalculatorTest.py

    r144fe21 r9ce69ec  
    8181        self.assertEqual(self.widget.ui.editMolecularFormula.styleSheet(), '') 
    8282        self.assertEqual(self.widget.model.columnCount(), 1) 
    83         self.assertEqual(self.widget.model.rowCount(), 12) 
     83        self.assertEqual(self.widget.model.rowCount(), 11) 
    8484        self.assertEqual(self.widget.sizePolicy().Policy(), QtWidgets.QSizePolicy.Fixed) 
    8585 
    8686    def testSimpleEntry(self): 
    8787        ''' Default compound calculations ''' 
    88  
    89         self.widget.show() 
    9088 
    9189        self.widget.ui.editMassDensity.clear() 
     
    102100 
    103101        # Change mass density 
    104         self.widget.ui.editWavelength.clear() 
    105         self.widget.ui.editWavelength.setText("666.0") 
     102        self.widget.ui.editNeutronWavelength.clear() 
     103        self.widget.ui.editNeutronWavelength.setText("666.0") 
    106104 
    107105        # Send shift-tab to update the molar volume field 
     
    130128 
    131129        # Assure the mass density field is set 
    132         self.assertEqual(self.widget.ui.editNeutronIncXs.text(), '43.4') 
     130        #self.assertEqual(self.widget.ui.editNeutronIncXs.text(), '43.4') 
     131        self.assertEqual(self.widget.ui.editNeutronIncXs.text(), '2.89') 
    133132 
    134133        # Reset the widget 
    135134        self.widget.modelReset() 
    136  
     135        
    137136        self.assertEqual(self.widget.ui.editMolecularFormula.text(), "H2O") 
    138137        self.assertEqual(self.widget.ui.editMassDensity.text(), "1") 
  • src/sas/qtgui/GUITests.py

    r7dd309a rccd2b87  
    180180        unittest.makeSuite(InvariantPerspectiveTest.InvariantPerspectiveTest,  'test'), 
    181181        #  Inversion 
    182         #unittest.makeSuite(InversionPerspectiveTest.InversionTest,  'test'), 
     182        unittest.makeSuite(InversionPerspectiveTest.InversionTest,  'test'), 
    183183        ) 
    184184    return unittest.TestSuite(suites) 
  • src/sas/qtgui/MainWindow/DataExplorer.py

    r2b8286c r30bed93  
    574574        new_plots = [] 
    575575        for item, plot in plots.items(): 
    576             if self.updatePlot(plot) and filename != plot.name: 
     576            if self.updatePlot(plot): 
     577                # Don't create plots which are already displayed 
    577578                continue 
    578579            # Don't plot intermediate results, e.g. P(Q), S(Q) 
    579580            match = GuiUtils.theory_plot_ID_pattern.match(plot.id) 
    580             # 2nd match group contains the identifier for the intermediate result, if present (e.g. "[P(Q)]") 
     581            # 2nd match group contains the identifier for the intermediate 
     582            # result, if present (e.g. "[P(Q)]") 
    581583            if match and match.groups()[1] != None: 
    582584                continue 
    583             # Don't include plots from different fitpages, but always include the original data 
    584             if fitpage_name in plot.name or filename == plot.name: 
    585                 # 'sophisticated' test to generate standalone plot for residuals 
    586                 if 'esiduals' in plot.title: 
     585            # Don't include plots from different fitpages, 
     586            # but always include the original data 
     587            if (fitpage_name in plot.name 
     588                    or filename in plot.name 
     589                    or filename == plot.filename): 
     590                # Residuals get their own plot 
     591                if plot.plot_role == Data1D.ROLE_RESIDUAL: 
    587592                    plot.yscale='linear' 
    588593                    self.plotData([(item, plot)]) 
     
    597602        Forces display of charts for the given data set 
    598603        """ 
    599         plot_to_show = data_list[0] 
    600         # passed plot is used ONLY to figure out its title, 
    601         # so all the charts related by it can be pulled from  
    602         # the data explorer indices. 
    603         filename = plot_to_show.filename 
    604         self.displayFile(filename=filename, is_data=plot_to_show.is_data, id=id) 
     604        # data_list = [QStandardItem, Data1D/Data2D] 
     605        plot_to_show = data_list[1] 
     606        plot_item = data_list[0] 
     607 
     608        # plots to show 
     609        new_plots = [] 
     610 
     611        # Check if this is merely a plot update 
     612        if self.updatePlot(plot_to_show): 
     613            return 
     614 
     615        # Residuals get their own plot 
     616        if plot_to_show.plot_role == Data1D.ROLE_RESIDUAL: 
     617            plot_to_show.yscale='linear' 
     618            self.plotData([(plot_item, plot_to_show)]) 
     619        elif plot_to_show.plot_role == Data1D.ROLE_DELETABLE: 
     620            # No plot 
     621            return 
     622        else: 
     623            # Plots with main data points on the same chart 
     624            # Get the main data plot 
     625            main_data = GuiUtils.dataFromItem(plot_item.parent()) 
     626            if main_data is None: 
     627                # Try the current item 
     628                main_data = GuiUtils.dataFromItem(plot_item) 
     629            if main_data is not None: 
     630                new_plots.append((plot_item, main_data)) 
     631            new_plots.append((plot_item, plot_to_show)) 
     632 
     633        if new_plots: 
     634            self.plotData(new_plots) 
    605635 
    606636    def addDataPlot2D(self, plot_set, item): 
     
    686716 
    687717        # Update the active chart list 
    688         #self.active_plots[new_plot.data.id] = new_plot 
     718        self.active_plots[new_plot.data.name] = new_plot 
    689719 
    690720    def appendPlot(self): 
     
    700730        # old plot data 
    701731        plot_id = str(self.cbgraph.currentText()) 
    702  
    703         assert plot_id in PlotHelper.currentPlots(), "No such plot: %s"%(plot_id) 
     732        try: 
     733            assert plot_id in PlotHelper.currentPlots(), "No such plot: %s"%(plot_id) 
     734        except: 
     735            return 
    704736 
    705737        old_plot = PlotHelper.plotById(plot_id) 
     
    729761        data_id = data.name 
    730762        if data_id in ids_keys: 
    731             self.active_plots[data_id].replacePlot(data_id, data) 
     763            # We have data, let's replace data that needs replacing 
     764            if data.plot_role != Data1D.ROLE_DATA: 
     765                self.active_plots[data_id].replacePlot(data_id, data) 
    732766            return True 
    733767        elif data_id in ids_vals: 
    734             list(self.active_plots.values())[ids_vals.index(data_id)].replacePlot(data_id, data) 
     768            if data.plot_role != Data1D.ROLE_DATA: 
     769                list(self.active_plots.values())[ids_vals.index(data_id)].replacePlot(data_id, data) 
    735770            return True 
    736771        return False 
     
    946981        self.context_menu.addAction(self.actionQuick3DPlot) 
    947982        self.context_menu.addAction(self.actionEditMask) 
     983        #self.context_menu.addSeparator() 
     984        #self.context_menu.addAction(self.actionFreezeResults) 
    948985        self.context_menu.addSeparator() 
    949986        self.context_menu.addAction(self.actionDelete) 
     
    957994        self.actionEditMask.triggered.connect(self.showEditDataMask) 
    958995        self.actionDelete.triggered.connect(self.deleteItem) 
     996        self.actionFreezeResults.triggered.connect(self.freezeSelectedItems) 
    959997 
    960998    def onCustomContextMenu(self, position): 
     
    9771015        self.actionQuick3DPlot.setEnabled(is_2D) 
    9781016        self.actionEditMask.setEnabled(is_2D) 
     1017 
     1018        # Freezing 
     1019        # check that the selection has inner items 
     1020        freeze_enabled = False 
     1021        if model_item.parent() is not None: 
     1022            freeze_enabled = True 
     1023        self.actionFreezeResults.setEnabled(freeze_enabled) 
     1024 
    9791025        # Fire up the menu 
    9801026        self.context_menu.exec_(self.current_view.mapToGlobal(position)) 
     
    11121158        mask_editor.exec_() 
    11131159 
     1160    def freezeItem(self, item=None): 
     1161        """ 
     1162        Freeze given item 
     1163        """ 
     1164        if item is None: 
     1165            return 
     1166        self.model.beginResetModel() 
     1167        new_item = self.cloneTheory(item) 
     1168        self.model.appendRow(new_item) 
     1169        self.model.endResetModel() 
     1170 
     1171    def freezeSelectedItems(self): 
     1172        """ 
     1173        Freeze selected items 
     1174        """ 
     1175        indices = self.treeView.selectedIndexes() 
     1176 
     1177        proxy = self.treeView.model() 
     1178        model = proxy.sourceModel() 
     1179 
     1180        for index in indices: 
     1181            row_index = proxy.mapToSource(index) 
     1182            item_to_copy = model.itemFromIndex(row_index) 
     1183            if item_to_copy and item_to_copy.isCheckable(): 
     1184                self.freezeItem(item_to_copy) 
     1185 
    11141186    def deleteItem(self): 
    11151187        """ 
     
    12721344 
    12731345        # Caption for the theories 
    1274         checkbox_item.setChild(2, QtGui.QStandardItem("THEORIES")) 
     1346        checkbox_item.setChild(2, QtGui.QStandardItem("FIT RESULTS")) 
    12751347 
    12761348        # New row in the model 
  • src/sas/qtgui/MainWindow/DataManager.py

    r4e255d1 ra54bbf2b  
    118118        new_plot.path = path 
    119119        new_plot.list_group_id = [] 
     120        # Assign the plot role to data 
     121        new_plot.plot_role = Data1D.ROLE_DATA 
    120122        ##post data to plot 
    121123        # plot data 
  • src/sas/qtgui/MainWindow/GuiManager.py

    r5b144c6 rdad086f  
    1212from twisted.internet import reactor 
    1313# General SAS imports 
     14from sas import get_local_config, get_custom_config 
    1415from sas.qtgui.Utilities.ConnectionProxy import ConnectionProxy 
    1516from sas.qtgui.Utilities.SasviewLogger import setup_qt_logging 
     
    118119        self.dockedFilesWidget.setWidget(self.filesWidget) 
    119120 
    120         # Disable maximize/minimize and close buttons 
    121         self.dockedFilesWidget.setFeatures(QDockWidget.NoDockWidgetFeatures) 
    122  
    123         #self._workspace.workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget) 
     121        # Modify menu items on widget visibility change 
     122        self.dockedFilesWidget.visibilityChanged.connect(self.updateContextMenus) 
     123 
    124124        self._workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget) 
    125125        self._workspace.resizeDocks([self.dockedFilesWidget], [305], Qt.Horizontal) 
     
    137137        self.aboutWidget = AboutBox() 
    138138        self.categoryManagerWidget = CategoryManager(self._parent, manager=self) 
    139         self.welcomePanel = WelcomePanel() 
    140139        self.grid_window = None 
    141140        self._workspace.toolBar.setVisible(LocalConfig.TOOLBAR_SHOW) 
     
    165164            logger.error(traceback.format_exc()) 
    166165 
     166    def updateContextMenus(self, visible=False): 
     167        """ 
     168        Modify the View/Data Explorer menu item text on widget visibility 
     169        """ 
     170        if visible: 
     171            self._workspace.actionHide_DataExplorer.setText("Hide Data Explorer") 
     172        else: 
     173            self._workspace.actionHide_DataExplorer.setText("Show Data Explorer") 
     174 
    167175    def statusBarSetup(self): 
    168176        """ 
     
    233241        perspective_width = perspective_size.width() 
    234242        self._current_perspective.resize(perspective_width, workspace_height-10) 
    235         # Resize the mdi area to match the widget within 
    236         subwindow.resize(subwindow.minimumSizeHint()) 
    237243 
    238244        self._current_perspective.show() 
     
    380386            self.communicate.statusBarUpdateSignal.emit(msg) 
    381387 
    382     def showWelcomeMessage(self): 
     388    def actionWelcome(self): 
    383389        """ Show the Welcome panel """ 
     390        self.welcomePanel = WelcomePanel() 
    384391        self._workspace.workspace.addSubWindow(self.welcomePanel) 
    385392        self.welcomePanel.show() 
     393 
     394    def showWelcomeMessage(self): 
     395        """ Show the Welcome panel, when required """ 
     396        # Assure the welcome screen is requested 
     397        show_welcome_widget = True 
     398        custom_config = get_custom_config() 
     399        if hasattr(custom_config, "WELCOME_PANEL_SHOW"): 
     400            if isinstance(custom_config.WELCOME_PANEL_SHOW, bool): 
     401                show_welcome_widget = custom_config.WELCOME_PANEL_SHOW 
     402            else: 
     403                logging.warning("WELCOME_PANEL_SHOW has invalid value in custom_config.py") 
     404        if show_welcome_widget: 
     405            self.actionWelcome() 
    386406 
    387407    def addCallbacks(self): 
     
    405425        Trigger definitions for all menu/toolbar actions. 
    406426        """ 
     427        # disable not yet fully implemented actions 
     428        self._workspace.actionOpen_Analysis.setEnabled(False) 
     429        self._workspace.actionUndo.setEnabled(False) 
     430        self._workspace.actionRedo.setEnabled(False) 
     431        self._workspace.actionReset.setEnabled(False) 
     432        self._workspace.actionStartup_Settings.setEnabled(False) 
     433        self._workspace.actionImage_Viewer.setEnabled(False) 
     434        self._workspace.actionCombine_Batch_Fit.setEnabled(False) 
     435        self._workspace.actionFit_Results.setEnabled(False) 
     436 
    407437        # File 
    408438        self._workspace.actionLoadData.triggered.connect(self.actionLoadData) 
     
    427457        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings) 
    428458        self._workspace.actionCategory_Manager.triggered.connect(self.actionCategory_Manager) 
     459        self._workspace.actionHide_DataExplorer.triggered.connect(self.actionHide_DataExplorer) 
    429460        # Tools 
    430461        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation) 
     
    469500        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge) 
    470501        self._workspace.actionAbout.triggered.connect(self.actionAbout) 
     502        self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome) 
    471503        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update) 
    472504 
     
    617649        pass 
    618650 
     651    def actionHide_DataExplorer(self): 
     652        """ 
     653        Toggle Data Explorer vsibility 
     654        """ 
     655        if self.dockedFilesWidget.isVisible(): 
     656            #self._workspace.actionHide_DataExplorer.setText("Show Data Explorer") 
     657            self.dockedFilesWidget.setVisible(False) 
     658        else: 
     659            #self._workspace.actionHide_DataExplorer.setText("Hide Data Explorer") 
     660            self.dockedFilesWidget.setVisible(True) 
     661        pass 
     662 
    619663    def actionStartup_Settings(self): 
    620664        """ 
     
    9581002        When setting a perspective, sets up the menu bar 
    9591003        """ 
     1004        self._workspace.actionReport.setEnabled(False) 
    9601005        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]): 
    9611006            self.checkAnalysisOption(self._workspace.actionFitting) 
     
    9671012            self._workspace.menubar.addAction(self._workspace.menuWindow.menuAction()) 
    9681013            self._workspace.menubar.addAction(self._workspace.menuHelp.menuAction()) 
     1014            self._workspace.actionReport.setEnabled(True) 
    9691015 
    9701016        elif isinstance(perspective, Perspectives.PERSPECTIVES["Invariant"]): 
  • src/sas/qtgui/MainWindow/UI/DataExplorerUI.ui

    rf4a6f2c r33b3e4d  
    508508   </property> 
    509509  </action> 
     510  <action name="actionFreezeResults"> 
     511   <property name="text"> 
     512    <string>Freeze Results</string> 
     513   </property> 
     514  </action> 
    510515 </widget> 
    511516 <resources/> 
  • src/sas/qtgui/MainWindow/UI/MainWindowUI.ui

    r2f14b5d rfc5d2d7f  
    7474    <addaction name="separator"/> 
    7575    <addaction name="actionHide_Toolbar"/> 
     76    <addaction name="actionHide_DataExplorer"/> 
    7677    <addaction name="separator"/> 
    7778    <addaction name="actionStartup_Settings"/> 
     
    147148    <addaction name="separator"/> 
    148149    <addaction name="actionAbout"/> 
     150    <addaction name="actionWelcomeWidget"/> 
     151    <addaction name="separator"/> 
    149152    <addaction name="actionCheck_for_update"/> 
    150153   </widget> 
     
    415418   </property> 
    416419  </action> 
    417     <action name="actionEditMask"> 
     420  <action name="actionEditMask"> 
    418421   <property name="text"> 
    419422    <string>Edit Mask</string> 
     
    549552  <action name="actionFreeze_Theory"> 
    550553   <property name="text"> 
    551     <string>Freeze Theory</string> 
     554    <string>Freeze Fit Results</string> 
     555   </property> 
     556  </action> 
     557  <action name="actionHide_DataExplorer"> 
     558   <property name="text"> 
     559    <string>Hide Data Explorer</string> 
     560   </property> 
     561  </action> 
     562  <action name="actionWelcomeWidget"> 
     563   <property name="text"> 
     564    <string>Welcome to SasView</string> 
    552565   </property> 
    553566  </action> 
  • src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py

    r144fe21 r768387e0  
    5353        self.assertIsInstance(self.manager.dockedFilesWidget, QDockWidget) 
    5454        self.assertIsInstance(self.manager.dockedFilesWidget.widget(), DataExplorerWindow) 
    55         self.assertEqual(self.manager.dockedFilesWidget.features(), QDockWidget.NoDockWidgetFeatures) 
    5655        self.assertEqual(self.manager._workspace.dockWidgetArea(self.manager.dockedFilesWidget), Qt.LeftDockWidgetArea) 
    5756 
  • src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py

    raed0532 r305114c  
    1313import webbrowser 
    1414 
     15from sas.qtgui.Perspectives.Fitting import FittingUtilities 
    1516import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    1617ALLOWED_OPERATORS = ['=','<','>','>=','<='] 
     
    3233        self.operator = '=' 
    3334 
     35        self.warning = self.lblWarning.text() 
    3436        self.setupData() 
     37        self.setupSignals() 
    3538        self.setupWidgets() 
    36         self.setupSignals() 
    3739        self.setupTooltip() 
    38  
    39         self.setFixedSize(self.minimumSizeHint()) 
    4040 
    4141        # Default focus is on OK 
     
    101101        # Find out the signal source 
    102102        source = self.sender().objectName() 
     103        param1 = self.cbParam1.currentText() 
     104        param2 = self.cbParam2.currentText() 
    103105        if source == "cbParam1": 
    104             self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText()) 
     106            self.txtParam.setText(self.tab_names[0] + ":" + param1) 
    105107        else: 
    106             self.txtConstraint.setText(self.tab_names[1] + "." + self.cbParam2.currentText()) 
     108            self.txtConstraint.setText(self.tab_names[1] + "." + param2) 
     109        # Check if any of the parameters are polydisperse 
     110        params_list = [param1, param2] 
     111        all_pars = [tab.model_parameters for tab in self.tabs] 
     112        is2Ds = [tab.is2D for tab in self.tabs] 
     113        txt = "" 
     114        for pars, is2D in zip(all_pars, is2Ds): 
     115            if any([FittingUtilities.isParamPolydisperse(p, pars, is2D) for p in params_list]): 
     116                # no parameters are pd - reset the text to not show the warning 
     117                txt = self.warning 
     118        self.lblWarning.setText(txt) 
     119 
    107120 
    108121    def onOperatorChange(self, index): 
  • src/sas/qtgui/Perspectives/Fitting/FittingLogic.py

    r5a96a72 radf1c2a  
    154154        new_plot.xaxis(_xaxis, _xunit) 
    155155        new_plot.yaxis(_yaxis, _yunit) 
     156 
     157        if component is not None: 
     158            new_plot.plot_role = Data1D.ROLE_DELETABLE #deletable 
    156159 
    157160        return new_plot 
  • src/sas/qtgui/Perspectives/Fitting/FittingOptions.py

    rff3b293 r8873ab7  
    6464        default_index = self.cbAlgorithm.findText(default_name) 
    6565        self.cbAlgorithm.setCurrentIndex(default_index) 
     66        # previous algorithm choice 
     67        self.previous_index = default_index 
    6668 
    6769        # Assign appropriate validators 
     
    121123 
    122124        # Convert the name into widget instance 
    123         widget_to_activate = eval(widget_name) 
     125        try: 
     126            widget_to_activate = eval(widget_name) 
     127        except AttributeError: 
     128            # We don't yet have this optimizer. 
     129            # Show message 
     130            msg = "This algorithm has not yet been implemented in SasView.\n" 
     131            msg += "Please choose a different algorithm" 
     132            QtWidgets.QMessageBox.warning(self, 
     133                                        'Warning', 
     134                                        msg, 
     135                                        QtWidgets.QMessageBox.Ok) 
     136            # Move the index to previous position 
     137            self.cbAlgorithm.setCurrentIndex(self.previous_index) 
     138            return 
     139 
    124140        index_for_this_id = self.stackedWidget.indexOf(widget_to_activate) 
    125141 
     
    133149        # OK has to be reinitialized to True 
    134150        self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) 
     151 
     152        # keep reference 
     153        self.previous_index = index 
    135154 
    136155    def onApply(self): 
     
    143162            # e.g. 'samples' for 'dream' is 'self.samples_dream' 
    144163            widget_name = 'self.'+option+'_'+self.current_fitter_id 
    145             line_edit = eval(widget_name) 
     164            try: 
     165                line_edit = eval(widget_name) 
     166            except AttributeError: 
     167                # Skip bumps monitors 
     168                continue 
    146169            if line_edit is None or not isinstance(line_edit, QtWidgets.QLineEdit): 
    147170                continue 
     
    165188                return 
    166189            try: 
    167                 new_value = widget.currentText() if isinstance(widget, QtWidgets.QComboBox) \ 
    168                     else float(widget.text()) 
     190                if isinstance(widget, QtWidgets.QComboBox): 
     191                    new_value = widget.currentText() 
     192                else: 
     193                    try: 
     194                        new_value = int(widget.text()) 
     195                    except ValueError: 
     196                        new_value = float(widget.text()) 
     197                #new_value = widget.currentText() if isinstance(widget, QtWidgets.QComboBox) \ 
     198                #    else float(widget.text()) 
    169199                self.config.values[self.current_fitter_id][option] = new_value 
    170200            except ValueError: 
  • src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py

    r01b4877 r30bed93  
    2323 
    2424poly_header_tooltips = ['Select parameter for fitting', 
    25                         'Enter polydispersity ratio (STD/mean). ' 
    26                         'STD: standard deviation from the mean value', 
     25                        'Enter polydispersity ratio (Std deviation/mean).\n'+ 
     26                        'For angles this can be either std deviation or half width (for uniform distributions) in degrees', 
    2727                        'Enter minimum value for parameter', 
    2828                        'Enter maximum value for parameter', 
     
    128128 
    129129            # Find param in volume_params 
    130             for p in parameters.form_volume_parameters: 
     130            poly_pars = copy.deepcopy(parameters.form_volume_parameters) 
     131            if is2D: 
     132                poly_pars += parameters.orientation_parameters 
     133            for p in poly_pars: 
    131134                if p.name != param.name: 
    132135                    continue 
     
    567570    return residuals 
    568571 
     572def plotPolydispersities(model): 
     573    plots = [] 
     574    if model is None: 
     575        return plots 
     576    # test for model being a sasmodels.sasview_model.SasviewModel? 
     577    for name in model.dispersion.keys(): 
     578        xarr, yarr = model.get_weights(name) 
     579        if len(xarr) <= 1: # param name not found or no polydisp. 
     580            continue 
     581        # create Data1D as in residualsData1D() and fill x/y members 
     582        # similar to FittingLogic._create1DPlot() but different data/axes 
     583        data1d = Data1D(x=xarr, y=yarr) 
     584        xunit = model.details[name][0] 
     585        data1d.xaxis(r'\rm{{{}}}'.format(name.replace('_', '\_')), xunit) 
     586        data1d.yaxis(r'\rm{weight}', 'normalized') 
     587        data1d.scale = 'linear' 
     588        data1d.symbol = 'Line' 
     589        data1d.name = "{} polydispersity".format(name) 
     590        data1d.id = data1d.name # placeholder, has to be completed later 
     591        data1d.plot_role = Data1D.ROLE_RESIDUAL 
     592        plots.append(data1d) 
     593    return plots 
     594 
    569595def binary_encode(i, digits): 
    570596    return [i >> d & 1 for d in range(digits)] 
     
    777803 
    778804    return output_string 
     805 
     806def isParamPolydisperse(param_name, kernel_params, is2D=False): 
     807    """ 
     808    Simple lookup for polydispersity for the given param name 
     809    """ 
     810    parameters = kernel_params.form_volume_parameters 
     811    if is2D: 
     812        parameters += kernel_params.orientation_parameters 
     813    has_poly = False 
     814    for param in parameters: 
     815        if param.name==param_name and param.polydisperse: 
     816            has_poly = True 
     817            break 
     818    return has_poly 
     819 
  • 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): 
  • src/sas/qtgui/Perspectives/Fitting/MultiConstraint.py

    raed0532 r305114c  
    2828 
    2929        self.setupUi(self) 
    30         self.setFixedSize(self.minimumSizeHint()) 
    3130        self.setModal(True) 
    3231        self.params = params 
  • src/sas/qtgui/Perspectives/Fitting/UI/ComplexConstraintUI.ui

    ra90c9c5 r1738173  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>406</width> 
    10     <height>167</height> 
     9    <width>367</width> 
     10    <height>199</height> 
    1111   </rect> 
    1212  </property> 
     
    2121     </property> 
    2222     <layout class="QGridLayout" name="gridLayout"> 
     23      <item row="1" column="0"> 
     24       <layout class="QHBoxLayout" name="horizontalLayout_2"> 
     25        <item> 
     26         <widget class="QLabel" name="txtParam"> 
     27          <property name="sizePolicy"> 
     28           <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> 
     29            <horstretch>0</horstretch> 
     30            <verstretch>0</verstretch> 
     31           </sizepolicy> 
     32          </property> 
     33          <property name="text"> 
     34           <string>param1</string> 
     35          </property> 
     36         </widget> 
     37        </item> 
     38        <item> 
     39         <widget class="QLabel" name="txtOperator"> 
     40          <property name="text"> 
     41           <string>=</string> 
     42          </property> 
     43         </widget> 
     44        </item> 
     45        <item> 
     46         <widget class="QLineEdit" name="txtConstraint"> 
     47          <property name="text"> 
     48           <string/> 
     49          </property> 
     50         </widget> 
     51        </item> 
     52       </layout> 
     53      </item> 
    2354      <item row="0" column="0"> 
    2455       <layout class="QHBoxLayout" name="horizontalLayout"> 
     
    98129       </layout> 
    99130      </item> 
    100       <item row="1" column="0"> 
    101        <spacer name="verticalSpacer"> 
    102         <property name="orientation"> 
    103          <enum>Qt::Vertical</enum> 
    104         </property> 
    105         <property name="sizeHint" stdset="0"> 
    106          <size> 
    107           <width>20</width> 
    108           <height>25</height> 
    109          </size> 
    110         </property> 
    111        </spacer> 
    112       </item> 
    113       <item row="2" column="0"> 
    114        <layout class="QHBoxLayout" name="horizontalLayout_2"> 
    115         <item> 
    116          <widget class="QLabel" name="txtParam"> 
    117           <property name="sizePolicy"> 
    118            <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> 
    119             <horstretch>0</horstretch> 
    120             <verstretch>0</verstretch> 
    121            </sizepolicy> 
    122           </property> 
    123           <property name="text"> 
    124            <string>param1</string> 
    125           </property> 
    126          </widget> 
    127         </item> 
    128         <item> 
    129          <widget class="QLabel" name="txtOperator"> 
    130           <property name="text"> 
    131            <string>=</string> 
    132           </property> 
    133          </widget> 
    134         </item> 
    135         <item> 
    136          <widget class="QLineEdit" name="txtConstraint"> 
    137           <property name="text"> 
    138            <string/> 
    139           </property> 
    140          </widget> 
    141         </item> 
    142        </layout> 
    143       </item> 
    144131     </layout> 
    145132    </widget> 
    146133   </item> 
    147134   <item row="1" column="0"> 
     135    <widget class="QLabel" name="lblWarning"> 
     136     <property name="text"> 
     137      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;Warning! Polydisperse parameter selected.&lt;br/&gt;&lt;/span&gt;Constraints involving polydisperse parameters only apply to&lt;br/&gt;starting values and are not re-applied during size or angle polydispersity&lt;br/&gt;integrations. To do so requires creating a custom model.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     138     </property> 
     139    </widget> 
     140   </item> 
     141   <item row="2" column="0"> 
     142    <spacer name="verticalSpacer_2"> 
     143     <property name="orientation"> 
     144      <enum>Qt::Vertical</enum> 
     145     </property> 
     146     <property name="sizeHint" stdset="0"> 
     147      <size> 
     148       <width>20</width> 
     149       <height>9</height> 
     150      </size> 
     151     </property> 
     152    </spacer> 
     153   </item> 
     154   <item row="3" column="0"> 
    148155    <layout class="QHBoxLayout" name="horizontalLayout_3"> 
    149156     <item> 
  • src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui

    ra2e8ea5 rc928e81  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>566</width> 
    10     <height>646</height> 
     9    <width>651</width> 
     10    <height>540</height> 
    1111   </rect> 
    1212  </property> 
  • src/sas/qtgui/Perspectives/Fitting/UI/MultiConstraintUI.ui

    ra90c9c5 r1738173  
    1010    <x>0</x> 
    1111    <y>0</y> 
    12     <width>426</width> 
    13     <height>162</height> 
     12    <width>369</width> 
     13    <height>201</height> 
    1414   </rect> 
    1515  </property> 
    1616  <property name="sizePolicy"> 
    17    <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> 
     17   <sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding"> 
    1818    <horstretch>0</horstretch> 
    1919    <verstretch>0</verstretch> 
     
    119119   </item> 
    120120   <item row="2" column="0"> 
     121    <widget class="QLabel" name="lblWarning"> 
     122     <property name="text"> 
     123      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;Warning! Polydisperse parameter selected.&lt;br/&gt;&lt;/span&gt;Constraints involving polydisperse parameters only apply to&lt;br/&gt;starting values and are not re-applied during size or angle polydispersity&lt;br/&gt;integrations. To do so requires creating a custom model.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     124     </property> 
     125    </widget> 
     126   </item> 
     127   <item row="3" column="0"> 
    121128    <spacer name="verticalSpacer"> 
    122129     <property name="orientation"> 
     
    131138    </spacer> 
    132139   </item> 
    133    <item row="3" column="0"> 
     140   <item row="4" column="0"> 
    134141    <layout class="QHBoxLayout" name="horizontalLayout_2"> 
    135142     <item> 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py

    rf712bf30 r8faac15  
    393393 
    394394        # Test tooltips 
    395         self.assertEqual(len(self.widget._poly_model.header_tooltips), 8) 
     395        self.assertEqual(len(self.widget._poly_model.header_tooltips), 9) 
    396396 
    397397        header_tooltips = ['Select parameter for fitting', 
    398                              'Enter polydispersity ratio (STD/mean). ' 
    399                              'STD: standard deviation from the mean value', 
    400                              'Enter minimum value for parameter', 
    401                              'Enter maximum value for parameter', 
    402                              'Enter number of points for parameter', 
    403                              'Enter number of sigmas parameter', 
    404                              'Select distribution function', 
    405                              'Select filename with user-definable distribution'] 
     398                            'Enter polydispersity ratio (Std deviation/mean).\n'+ 
     399                            'For angles this can be either std deviation or half width (for uniform distributions) in degrees', 
     400                            'Enter minimum value for parameter', 
     401                            'Enter maximum value for parameter', 
     402                            'Enter number of points for parameter', 
     403                            'Enter number of sigmas parameter', 
     404                            'Select distribution function', 
     405                            'Select filename with user-definable distribution'] 
    406406        for column, tooltip in enumerate(header_tooltips): 
    407407             self.assertEqual(self.widget._poly_model.headerData( column, 
     
    619619        # Set to 0 
    620620        self.widget.lstParams.indexWidget(func_index).setCurrentIndex(0) 
    621         self.assertEqual(self.widget._model_model.rowCount(), last_row - 2) # 2 fewer rows than default 
     621        self.assertEqual(self.widget._model_model.rowCount(), last_row - 2) 
    622622 
    623623    def testPlotTheory(self): 
     
    657657        self.assertEqual(spy.count(), 0) 
    658658 
    659     def testPlotData(self): 
     659    def notestPlotData(self): 
    660660        """ 
    661661        See that data item can produce a chart 
     
    908908        self.assertFalse(self.widget.tabFitting.isTabEnabled(4)) 
    909909 
    910     def testReadFitPage2D(self): 
     910    # to be fixed after functionality is ready 
     911    def notestReadFitPage2D(self): 
    911912        """ 
    912913        Read in the fitpage object and restore state 
  • src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py

    raed0532 rdee9e5f  
    233233 
    234234        plot_data = GuiUtils.plotsFromCheckedItems(self._manager.filesWidget.model) 
     235        #self.communicate.plotRequestedSignal.emit([item, plot], self.tab_id) 
    235236 
    236237        self._manager.filesWidget.plotData(plot_data) 
     
    347348                extrapolated_data.name = title 
    348349                extrapolated_data.title = title 
     350                extrapolated_data.style = "Line" 
     351                extrapolated_data.has_errors = False 
     352                extrapolated_data.plot_role = Data1D.ROLE_DEFAULT 
    349353 
    350354                # copy labels and units of axes for plotting 
     
    378382                high_out_data.name = title 
    379383                high_out_data.title = title 
     384                high_out_data.style = "Line" 
     385                high_out_data.has_errors = False 
     386                high_out_data.plot_role = Data1D.ROLE_DEFAULT 
    380387 
    381388                # copy labels and units of axes for plotting 
  • src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py

    rb0ba43e r9f2f713  
    4242        self.parent = parent 
    4343 
    44         self.setWindowTitle("Dₐₓ Explorer") 
     44        self.setWindowTitle("Dmax Explorer") 
    4545 
    4646        self.pr_state = pr_state 
     
    116116        bck = [] 
    117117        chi2 = [] 
    118  
     118        plotable_xs = [] #Introducing this to make sure size of x and y for plotting is the same.8 
    119119        try: 
    120120            dmin = float(self.model.item(W.DMIN).text()) 
     
    128128 
    129129        original = self.pr_state.d_max 
     130 
    130131        for x in xs: 
    131132            self.pr_state.d_max = x 
     
    140141                bck.append(self.pr_state.background) 
    141142                chi2.append(self.pr_state.chi2) 
     143                plotable_xs.append(x) 
    142144            except Exception as ex: 
    143145                # This inversion failed, skip this D_max value 
     
    155157            logger.error(msg) 
    156158 
    157         plotter = self.model.item(W.VARIABLE).text() 
    158         y_label = y_unit = "" 
     159        plotter = self.dependentVariable.currentText() 
    159160        x_label = "D_{max}" 
    160161        x_unit = "A" 
     
    188189            y_unit = "a.u." 
    189190 
    190         data = Data1D(xs, ys) 
     191        data = Data1D(plotable_xs, ys) 
    191192        if self.hasPlot: 
    192             self.plot.removePlot(None) 
     193            self.plot.removePlot(data.name) 
    193194        self.hasPlot = True 
    194195        data.title = plotter 
  • src/sas/qtgui/Perspectives/Inversion/InversionLogic.py

    r6da860a r3c4f02e  
    111111            new_plot.title = title 
    112112 
     113        new_plot.symbol = 'Line' 
     114        new_plot.hide_error = True 
     115 
    113116        return new_plot 
    114117 
  • src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py

    r2b8286c rdee9e5f  
    1414# pr inversion calculation elements 
    1515from sas.sascalc.pr.invertor import Invertor 
     16from sas.qtgui.Plotting.PlotterData import Data1D 
    1617# Batch calculation display 
    1718from sas.qtgui.Utilities.GridPanel import BatchInversionOutputPanel 
     
    4344    estimateSignal = QtCore.pyqtSignal(tuple) 
    4445    estimateNTSignal = QtCore.pyqtSignal(tuple) 
     46    estimateDynamicNTSignal = QtCore.pyqtSignal(tuple) 
     47    estimateDynamicSignal = QtCore.pyqtSignal(tuple) 
    4548    calculateSignal = QtCore.pyqtSignal(tuple) 
    4649 
     
    5255 
    5356        self._manager = parent 
     57        #Needed for Batch fitting 
     58        self._parent = parent 
    5459        self.communicate = parent.communicator() 
    5560        self.communicate.dataDeletedSignal.connect(self.removeData) 
     
    109114        # Set up the Widget Map 
    110115        self.setupMapper() 
     116 
     117        #Hidding calculate all buton 
     118        self.calculateAllButton.setVisible(False) 
    111119        # Set base window state 
    112120        self.setupWindow() 
     
    119127 
    120128    def allowBatch(self): 
    121         return True 
     129        return False 
    122130 
    123131    def setClosable(self, value=True): 
     
    194202        self.model.itemChanged.connect(self.model_changed) 
    195203        self.estimateNTSignal.connect(self._estimateNTUpdate) 
     204        self.estimateDynamicNTSignal.connect(self._estimateDynamicNTUpdate) 
     205        self.estimateDynamicSignal.connect(self._estimateDynamicUpdate) 
    196206        self.estimateSignal.connect(self._estimateUpdate) 
    197207        self.calculateSignal.connect(self._calculateUpdate) 
     208 
     209        self.maxDistanceInput.textEdited.connect(self.performEstimateDynamic) 
    198210 
    199211    def setupMapper(self): 
     
    309321                                            and not self.isCalculating) 
    310322        self.removeButton.setEnabled(self.logic.data_is_loaded) 
    311         self.explorerButton.setEnabled(self.logic.data_is_loaded 
    312                                        and np.all(self.logic.data.dy != 0)) 
     323        self.explorerButton.setEnabled(self.logic.data_is_loaded) 
    313324        self.stopButton.setVisible(self.isCalculating) 
    314325        self.regConstantSuggestionButton.setEnabled( 
     
    453464            # Create initial internal mappings 
    454465            self.logic.data = GuiUtils.dataFromItem(data) 
     466            if not isinstance(self.logic.data, Data1D): 
     467                msg = "P(r) perspective works for 1D data only" 
     468                logger.warning(msg) 
     469                continue 
    455470            # Estimate q range 
    456471            qmin, qmax = self.logic.computeDataRange() 
    457472            self._calculator.set_qmin(qmin) 
    458473            self._calculator.set_qmax(qmax) 
     474            if np.size(self.logic.data.dy) == 0 or np.all(self.logic.data.dy) == 0: 
     475                self.logic.data.dy = self._calculator.add_errors(self.logic.data.y) 
    459476            self.updateDataList(data) 
    460477            self.populateDataComboBox(self.logic.data.filename, data) 
    461478        self.dataList.setCurrentIndex(len(self.dataList) - 1) 
    462         self.setCurrentData(data) 
     479        #Checking for 1D again to mitigate the case when 2D data is last on the data list 
     480        if isinstance(self.logic.data, Data1D): 
     481            self.setCurrentData(data) 
    463482 
    464483    def updateDataList(self, dataRef): 
     
    501520        self.dataPlot = self._dataList[data_ref].get(DICT_KEYS[2]) 
    502521        self.performEstimate() 
     522 
     523    def updateDynamicGuiValues(self): 
     524        pr = self._calculator 
     525        alpha = self._calculator.suggested_alpha 
     526        self.model.setItem(WIDGETS.W_MAX_DIST, 
     527                            QtGui.QStandardItem("{:.4g}".format(pr.get_dmax()))) 
     528        self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha)) 
     529        self.noOfTermsSuggestionButton.setText( 
     530             "{:n}".format(self.nTermsSuggested)) 
     531 
     532        self.enableButtons() 
    503533 
    504534    def updateGuiValues(self): 
     
    520550        self.model.setItem(WIDGETS.W_MAX_DIST, 
    521551                           QtGui.QStandardItem("{:.4g}".format(pr.get_dmax()))) 
    522         self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha)) 
    523         self.noOfTermsSuggestionButton.setText( 
    524             "{:n}".format(self.nTermsSuggested)) 
    525552 
    526553        if isinstance(pr.chi2, np.ndarray): 
     
    544571        if self.prPlot is not None: 
    545572            title = self.prPlot.name 
     573            self.prPlot.plot_role = Data1D.ROLE_RESIDUAL 
    546574            GuiUtils.updateModelItemWithPlot(self._data, self.prPlot, title) 
    547             self.communicate.plotRequestedSignal.emit([self.prPlot], None) 
     575            self.communicate.plotRequestedSignal.emit([self._data,self.prPlot], None) 
    548576        if self.dataPlot is not None: 
    549577            title = self.dataPlot.name 
     578            self.dataPlot.plot_role = Data1D.ROLE_DEFAULT 
     579            self.dataPlot.symbol = "Line" 
     580            self.dataPlot.show_errors = False 
    550581            GuiUtils.updateModelItemWithPlot(self._data, self.dataPlot, title) 
    551             self.communicate.plotRequestedSignal.emit([self.dataPlot], None) 
     582            self.communicate.plotRequestedSignal.emit([self._data,self.dataPlot], None) 
    552583        self.enableButtons() 
    553584 
     
    633664 
    634665        pr = self._calculator.clone() 
    635         nfunc = self.getNFunc() 
    636         self.calcThread = CalcPr(pr, nfunc, 
     666        #Making sure that nfunc and alpha parameters are correctly initialized 
     667        pr.suggested_alpha = self._calculator.alpha 
     668        self.calcThread = CalcPr(pr, self.nTermsSuggested, 
    637669                                 error_func=self._threadError, 
    638670                                 completefn=self._calculateCompleted, 
     
    667699                                             error_func=self._threadError, 
    668700                                             completefn=self._estimateNTCompleted, 
     701                                             updatefn=None) 
     702        self.estimationThreadNT.queue() 
     703        self.estimationThreadNT.ready(2.5) 
     704 
     705    def performEstimateDynamicNT(self): 
     706        """ 
     707        Perform parameter estimation 
     708        """ 
     709        from .Thread import EstimateNT 
     710 
     711        self.updateCalculator() 
     712 
     713        # If a thread is already started, stop it 
     714        self.stopEstimateNTThread() 
     715 
     716        pr = self._calculator.clone() 
     717        # Skip the slit settings for the estimation 
     718        # It slows down the application and it doesn't change the estimates 
     719        pr.slit_height = 0.0 
     720        pr.slit_width = 0.0 
     721        nfunc = self.getNFunc() 
     722 
     723        self.estimationThreadNT = EstimateNT(pr, nfunc, 
     724                                             error_func=self._threadError, 
     725                                             completefn=self._estimateDynamicNTCompleted, 
    669726                                             updatefn=None) 
    670727        self.estimationThreadNT.queue() 
     
    693750        self.estimationThread.ready(2.5) 
    694751 
     752    def performEstimateDynamic(self): 
     753        """ 
     754            Perform parameter estimation 
     755        """ 
     756        from .Thread import EstimatePr 
     757 
     758        # If a thread is already started, stop it 
     759        self.stopEstimationThread() 
     760 
     761        self.estimationThread = EstimatePr(self._calculator.clone(), 
     762                                           self.getNFunc(), 
     763                                           error_func=self._threadError, 
     764                                           completefn=self._estimateDynamicCompleted, 
     765                                           updatefn=None) 
     766        self.estimationThread.queue() 
     767        self.estimationThread.ready(2.5) 
     768 
    695769    def stopEstimationThread(self): 
    696770        """ Stop the estimation thread if it exists and is running """ 
     
    705779        ''' Send a signal to the main thread for model update''' 
    706780        self.estimateSignal.emit((alpha, message, elapsed)) 
     781 
     782    def _estimateDynamicCompleted(self, alpha, message, elapsed): 
     783        ''' Send a signal to the main thread for model update''' 
     784        self.estimateDynamicSignal.emit((alpha, message, elapsed)) 
    707785 
    708786    def _estimateUpdate(self, output_tuple): 
     
    720798            logger.info(message) 
    721799        self.performEstimateNT() 
     800        self.performEstimateDynamicNT() 
     801 
     802    def _estimateDynamicUpdate(self, output_tuple): 
     803        """ 
     804        Parameter estimation completed, 
     805        display the results to the user 
     806 
     807        :param alpha: estimated best alpha 
     808        :param elapsed: computation time 
     809        """ 
     810        alpha, message, elapsed = output_tuple 
     811        self._calculator.alpha = alpha 
     812        self._calculator.elapsed += self._calculator.elapsed 
     813        if message: 
     814            logger.info(message) 
     815        self.performEstimateDynamicNT() 
    722816 
    723817    def _estimateNTCompleted(self, nterms, alpha, message, elapsed): 
    724818        ''' Send a signal to the main thread for model update''' 
    725819        self.estimateNTSignal.emit((nterms, alpha, message, elapsed)) 
     820 
     821    def _estimateDynamicNTCompleted(self, nterms, alpha, message, elapsed): 
     822        ''' Send a signal to the main thread for model update''' 
     823        self.estimateDynamicNTSignal.emit((nterms, alpha, message, elapsed)) 
    726824 
    727825    def _estimateNTUpdate(self, output_tuple): 
     
    747845            self.startThread() 
    748846 
     847    def _estimateDynamicNTUpdate(self, output_tuple): 
     848        """ 
     849        Parameter estimation completed, 
     850        display the results to the user 
     851 
     852        :param alpha: estimated best alpha 
     853        :param nterms: estimated number of terms 
     854        :param elapsed: computation time 
     855        """ 
     856        nterms, alpha, message, elapsed = output_tuple 
     857        self._calculator.elapsed += elapsed 
     858        self._calculator.suggested_alpha = alpha 
     859        self.nTermsSuggested = nterms 
     860        # Save useful info 
     861        self.updateDynamicGuiValues() 
     862        if message: 
     863            logger.info(message) 
     864        if self.isBatch: 
     865            self.acceptAlpha() 
     866            self.acceptNoTerms() 
     867            self.startThread() 
     868 
    749869    def _calculateCompleted(self, out, cov, pr, elapsed): 
    750870        ''' Send a signal to the main thread for model update''' 
  • src/sas/qtgui/Perspectives/Inversion/UI/DMaxExplorer.ui

    r8f83719f rcfd61f2  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>394</width> 
    10     <height>426</height> 
     9    <width>586</width> 
     10    <height>538</height> 
    1111   </rect> 
    1212  </property> 
     
    2121       <property name="orientation"> 
    2222        <enum>Qt::Vertical</enum> 
    23        </property> 
    24        <property name="sizeHint" stdset="0"> 
    25         <size> 
    26          <width>20</width> 
    27          <height>305</height> 
    28         </size> 
    2923       </property> 
    3024      </spacer> 
  • src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui

    r72ecbdf2 r68dc2873  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>390</width> 
    10     <height>409</height> 
     9    <width>446</width> 
     10    <height>480</height> 
    1111   </rect> 
    1212  </property> 
     
    740740      <widget class="QPushButton" name="calculateAllButton"> 
    741741       <property name="enabled"> 
    742         <bool>true</bool> 
     742        <bool>false</bool> 
    743743       </property> 
    744744       <property name="sizePolicy"> 
  • src/sas/qtgui/Perspectives/Inversion/UnitTesting/InversionPerspectiveTest.py

    r144fe21 rccd2b87  
    9999    def baseBatchState(self): 
    100100        """ Testing the base batch fitting state """ 
    101         self.assertTrue(self.widget.allowBatch()) 
     101        self.assertFalse(self.widget.allowBatch()) 
    102102        self.assertFalse(self.widget.isBatch) 
    103103        self.assertFalse(self.widget.calculateAllButton.isEnabled()) 
     
    304304        self.assertIsNotNone(self.widget.dmaxWindow) 
    305305        self.assertTrue(self.widget.dmaxWindow.isVisible()) 
    306         self.assertTrue(self.widget.dmaxWindow.windowTitle() == "Dₐₓ Explorer") 
     306        self.assertTrue(self.widget.dmaxWindow.windowTitle() == "Dmax Explorer") 
    307307 
    308308 
  • src/sas/qtgui/Plotting/Plotter.py

    r5b144c6 r863ebca  
    88import numpy as np 
    99from matplotlib.font_manager import FontProperties 
     10 
    1011from sas.qtgui.Plotting.PlotterData import Data1D 
    1112from sas.qtgui.Plotting.PlotterBase import PlotterBase 
     
    8485            if self.data.ytransform is None: 
    8586                self.data.ytransform = 'log10(y)' 
    86  
     87            #Added condition to Dmax explorer from P(r) perspective 
     88            if self.data._xaxis == 'D_{max}': 
     89                self.xscale = 'linear' 
    8790            # Transform data if required. 
    8891            if transform and (self.data.xtransform is not None or self.data.ytransform is not None): 
     
    222225            self.contextMenu.addAction("Reset Graph Range") 
    223226        # Add the title change for dialogs 
    224         #if self.parent: 
    225227        self.contextMenu.addSeparator() 
    226228        self.actionWindowTitle = self.contextMenu.addAction("Window Title") 
  • src/sas/qtgui/Plotting/Plotter2D.py

    rfce6c55 r676a430  
    445445                                        self.ymin, self.ymax)) 
    446446 
    447             cbax = self.figure.add_axes([0.84, 0.2, 0.02, 0.7]) 
     447            cbax = self.figure.add_axes([0.88, 0.2, 0.02, 0.7]) 
    448448 
    449449            # Current labels for axes 
  • src/sas/qtgui/Plotting/PlotterBase.py

    rd9150d8 r863ebca  
    1010 
    1111import matplotlib.pyplot as plt 
     12from matplotlib import rcParams 
    1213 
    1314DEFAULT_CMAP = pylab.cm.jet 
     
    2930        self.manager = manager 
    3031        self.quickplot = quickplot 
     32 
     33        # Set auto layout so x/y axis captions don't get cut off 
     34        rcParams.update({'figure.autolayout': True}) 
    3135 
    3236        #plt.style.use('ggplot') 
     
    106110 
    107111        self.contextMenu = QtWidgets.QMenu(self) 
    108  
     112        self.toolbar = NavigationToolbar(self.canvas, self) 
     113        layout.addWidget(self.toolbar) 
    109114        if not quickplot: 
    110115            # Add the toolbar 
    111             self.toolbar = NavigationToolbar(self.canvas, self) 
    112             layout.addWidget(self.toolbar) 
     116            self.toolbar.show() 
    113117            # Notify PlotHelper about the new plot 
    114118            self.upatePlotHelper() 
     119        else: 
     120            self.toolbar.hide() 
    115121 
    116122        self.setLayout(layout) 
     
    215221        self.actionCopyToClipboard = self.contextMenu.addAction("Copy to Clipboard") 
    216222        self.contextMenu.addSeparator() 
     223        self.actionToggleMenu = self.contextMenu.addAction("Toggle Navigation Menu") 
     224        self.contextMenu.addSeparator() 
     225 
    217226 
    218227        # Define the callbacks 
     
    220229        self.actionPrintImage.triggered.connect(self.onImagePrint) 
    221230        self.actionCopyToClipboard.triggered.connect(self.onClipboardCopy) 
     231        self.actionToggleMenu.triggered.connect(self.onToggleMenu) 
    222232 
    223233    def createContextMenu(self): 
     
    367377        self.manager.communicator.activeGraphName.emit((current_title, title)) 
    368378 
     379    def onToggleMenu(self): 
     380        """ 
     381        Toggle navigation menu visibility in the chart 
     382        """ 
     383        if self.toolbar.isVisible(): 
     384            self.toolbar.hide() 
     385        else: 
     386            self.toolbar.show() 
     387 
    369388    def offset_graph(self): 
    370389        """ 
  • src/sas/qtgui/Plotting/PlotterData.py

    rcee5c78 ra54bbf2b  
    1717    """ 
    1818    """ 
     19    ROLE_DATA=0 
     20    ROLE_DEFAULT=1 
     21    ROLE_DELETABLE=2 
     22    ROLE_RESIDUAL=3 
    1923    def __init__(self, x=None, y=None, dx=None, dy=None): 
    2024        """ 
     
    3539        self.title = "" 
    3640        self.scale = None 
     41        # plot_role: 
     42        # 0: data - no reload on param change 
     43        # 1: normal lifecycle (fit) 
     44        # 2: deletable on model change (Q(I), S(I)...) 
     45        # 3: separate chart on Show Plot (residuals) 
     46        self.plot_role = Data1D.ROLE_DEFAULT 
    3747         
    3848    def copy_from_datainfo(self, data1d): 
     
    184194        self.title = "" 
    185195        self.scale = None 
     196        # Always default 
     197        self.plot_role = Data1D.ROLE_DEFAULT 
    186198         
    187199    def copy_from_datainfo(self, data2d): 
  • src/sas/qtgui/Plotting/UnitTesting/Plotter2DTest.py

    r144fe21 r863ebca  
    146146        self.plotter.createContextMenuQuick() 
    147147        actions = self.plotter.contextMenu.actions() 
    148         self.assertEqual(len(actions), 7) 
     148        self.assertEqual(len(actions), 9) 
    149149 
    150150        # Trigger Print Image and make sure the method is called 
     
    158158 
    159159        # Trigger Toggle Grid and make sure the method is called 
    160         self.assertEqual(actions[4].text(), "Toggle Grid On/Off") 
     160        self.assertEqual(actions[6].text(), "Toggle Grid On/Off") 
    161161        self.plotter.ax.grid = MagicMock() 
    162         actions[4].trigger() 
     162        actions[6].trigger() 
    163163        self.assertTrue(self.plotter.ax.grid.called) 
    164164 
    165165        # Trigger Change Scale and make sure the method is called 
    166         self.assertEqual(actions[6].text(), "Toggle Linear/Log Scale") 
    167         FigureCanvas.draw_idle = MagicMock() 
    168         actions[6].trigger() 
     166        self.assertEqual(actions[8].text(), "Toggle Linear/Log Scale") 
     167        FigureCanvas.draw_idle = MagicMock() 
     168        actions[8].trigger() 
    169169        self.assertTrue(FigureCanvas.draw_idle.called) 
    170170 
  • src/sas/qtgui/Plotting/UnitTesting/PlotterBaseTest.py

    r144fe21 r863ebca  
    8484        self.assertTrue(PlotHelper.deletePlot.called) 
    8585 
    86     def testOnImagePrint(self): 
     86    def notestOnImagePrint(self): 
    8787        ''' test the workspace print ''' 
    8888        QtGui.QPainter.end = MagicMock() 
     
    124124 
    125125        actions = self.plotter.contextMenu.actions() 
    126         self.assertEqual(len(actions), 4) 
     126        self.assertEqual(len(actions), 6) 
    127127 
    128128        # Trigger Print Image and make sure the method is called 
     
    146146        # Make sure clipboard got updated. 
    147147        self.assertTrue(self.clipboard_called) 
     148 
     149        # Trigger toggle navigation bar and make sure the method is called 
     150        self.assertEqual(actions[4].text(), "Toggle Navigation Menu") 
     151        isShown = self.plotter.toolbar.isVisible() 
     152        self.assertTrue(isShow) 
     153        actions[4].trigger() 
     154        isShown = self.plotter.toolbar.isVisible() 
     155        self.assertFalse(isShow) 
     156        actions[4].trigger() 
     157        isShown = self.plotter.toolbar.isVisible() 
     158        self.assertTrue(isShow) 
     159 
    148160 
    149161    def testOnWindowsTitle(self): 
  • src/sas/qtgui/Plotting/UnitTesting/PlotterTest.py

    r5b144c6 r863ebca  
    103103        self.plotter.createContextMenuQuick() 
    104104        actions = self.plotter.contextMenu.actions() 
    105         self.assertEqual(len(actions), 7) 
     105        self.assertEqual(len(actions), 9) 
    106106 
    107107        # Trigger Print Image and make sure the method is called 
     
    115115 
    116116        # Trigger Toggle Grid and make sure the method is called 
    117         self.assertEqual(actions[4].text(), "Toggle Grid On/Off") 
     117        self.assertEqual(actions[6].text(), "Toggle Grid On/Off") 
    118118        self.plotter.ax.grid = MagicMock() 
    119         actions[4].trigger() 
     119        actions[6].trigger() 
    120120        self.assertTrue(self.plotter.ax.grid.called) 
    121121 
    122122        # Trigger Change Scale and make sure the method is called 
    123         self.assertEqual(actions[6].text(), "Change Scale") 
     123        self.assertEqual(actions[8].text(), "Change Scale") 
    124124        self.plotter.properties.exec_ = MagicMock(return_value=QtWidgets.QDialog.Rejected) 
    125         actions[6].trigger() 
     125        actions[8].trigger() 
    126126        self.assertTrue(self.plotter.properties.exec_.called) 
    127127 
  • src/sas/qtgui/Utilities/GridPanel.py

    r3c6ecd9 r4fbf0db  
    5454        # Fill in the table from input data 
    5555        self.setupTable(widget=self.tblParams, data=output_data) 
     56        #TODO: This is not what was inteded to be. 
    5657        if output_data is not None: 
    5758            # Set a table tooltip describing the model 
    58             model_name = output_data[0][0].model.id 
     59            model_name = list(output_data.keys())[0] 
    5960            self.tabWidget.setTabToolTip(0, model_name) 
    6061 
     
    418419    def __init__(self, parent = None, output_data=None): 
    419420 
    420         super(BatchInversionOutputPanel, self).__init__(parent, output_data) 
     421        super(BatchInversionOutputPanel, self).__init__(parent._parent, output_data) 
    421422        _translate = QtCore.QCoreApplication.translate 
    422423        self.setWindowTitle(_translate("GridPanelUI", "Batch P(r) Results")) 
    423424 
    424     def setupTable(self, data): 
     425    def setupTable(self, widget=None,  data=None): 
    425426        """ 
    426427        Create tablewidget items and show them, based on params 
     
    433434                      'Calc. Time [sec]'] 
    434435 
     436        if data is None: 
     437            return 
    435438        keys = data.keys() 
    436439        rows = len(keys) 
  • src/sas/qtgui/Utilities/GuiUtils.py

    r5d28d6b r9ce69ec  
    322322    assert isinstance(item, QtGui.QStandardItem) 
    323323 
     324    # lists of plots names/ids for all deletable plots on item 
    324325    names = [p.name for p in new_plots if p.name is not None] 
    325326    ids = [p.id for p in new_plots if p.id is not None] 
     
    329330    for index in range(item.rowCount()): 
    330331        plot_item = item.child(index) 
    331         if plot_item.isCheckable(): 
    332             plot_data = plot_item.child(0).data() 
    333             if (plot_data.id is not None) and (plot_data.id not in ids) and (plot_data.name not in names): 
    334                 items_to_delete.append(plot_item) 
     332        if not plot_item.isCheckable(): 
     333            continue 
     334        plot_data = plot_item.child(0).data() 
     335        if (plot_data.id is not None) and \ 
     336            (plot_data.id not in ids) and \ 
     337            (plot_data.name not in names) and \ 
     338            (plot_data.plot_role == Data1D.ROLE_DELETABLE): 
     339            items_to_delete.append(plot_item) 
    335340 
    336341    for plot_item in items_to_delete: 
     
    562567    The assumption - data stored in SasView standard, in child 0 
    563568    """ 
    564     return item.child(0).data() 
     569    try: 
     570        data = item.child(0).data() 
     571    except AttributeError: 
     572        data = None 
     573    return data 
    565574 
    566575def openLink(url): 
  • src/sas/sascalc/pr/invertor.py

    rb8080e1 reeea6a3  
    7171        A[j][i] = (Fourier transformed base function for point j) 
    7272 
    73     We them choose a number of r-points, n_r, to evaluate the second 
     73    We then choose a number of r-points, n_r, to evaluate the second 
    7474    derivative of P(r) at. This is used as our regularization term. 
    7575    For a vector r of length n_r, the following n_r rows are set to :: 
     
    144144        x, y, err, d_max, q_min, q_max and alpha 
    145145        """ 
    146         if   name == 'x': 
     146        if name == 'x': 
    147147            if 0.0 in value: 
    148148                msg = "Invertor: one of your q-values is zero. " 
     
    227227        return None 
    228228 
     229    def add_errors(self, yvalues): 
     230        """ 
     231        Adds errors to data set is they are not avaialble 
     232        :return: 
     233        """ 
     234        stats_errors = np.zeros(len(yvalues)) 
     235        for i in range(len(yvalues)): 
     236            # Scale the error so that we can fit over several decades of Q 
     237            scale = 0.05 * np.sqrt(yvalues[i]) 
     238            min_err = 0.01 * yvalues[i] 
     239            stats_errors[i] = scale * np.sqrt(np.fabs(yvalues[i])) + min_err 
     240        logger.warning("Simulated errors have been added to the data set\n") 
     241        return stats_errors 
     242 
    229243    def clone(self): 
    230244        """ 
     
    268282            A[i][j] = (Fourier transformed base function for point j) 
    269283 
    270         We them choose a number of r-points, n_r, to evaluate the second 
     284        We then choose a number of r-points, n_r, to evaluate the second 
    271285        derivative of P(r) at. This is used as our regularization term. 
    272286        For a vector r of length n_r, the following n_r rows are set to :: 
Note: See TracChangeset for help on using the changeset viewer.