Changeset d4dac80 in sasview


Ignore:
Timestamp:
Apr 25, 2018 4:59:33 AM (6 years ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
017b285
Parents:
93c79b5
git-author:
Piotr Rozyczko <rozyczko@…> (03/22/18 14:46:19)
git-committer:
Piotr Rozyczko <rozyczko@…> (04/25/18 04:59:33)
Message:

Merge branch 'ESS_GUI' into ESS_GUI_better_batch

Location:
src/sas/qtgui
Files:
15 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/MainWindow/DataExplorer.py

    rd6e38661 rd4dac80  
    476476        self.chkBatch.setEnabled(self.parent.perspective().allowBatch()) 
    477477 
     478    def itemFromFilename(self, filename): 
     479        """ 
     480        Retrieves model item corresponding to the given filename 
     481        """ 
     482        item = GuiUtils.itemFromFilename(filename, self.model) 
     483        return item 
     484 
    478485    def displayFile(self, filename=None, is_data=True): 
    479486        """ 
  • src/sas/qtgui/MainWindow/GuiManager.py

    r3b8cc00 rd4dac80  
    2121from sas.qtgui.Utilities.TabbedModelEditor import TabbedModelEditor 
    2222from sas.qtgui.Utilities.PluginManager import PluginManager 
     23from sas.qtgui.Utilities.GridPanel import BatchOutputPanel 
     24 
    2325from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements 
    2426from sas.qtgui.MainWindow.AboutBox import AboutBox 
     
    130132        self.aboutWidget = AboutBox() 
    131133        self.welcomePanel = WelcomePanel() 
     134        self.grid_window = None 
    132135 
    133136        # Add calculators - floating for usability 
     
    263266            msg = "Guiframe does not have a current perspective" 
    264267            logging.info(msg) 
     268 
     269    def findItemFromFilename(self, filename): 
     270        """ 
     271        Queries the data explorer for the index corresponding to the filename within 
     272        """ 
     273        return self.filesWidget.itemFromFilename(filename) 
    265274 
    266275    def quitApplication(self): 
     
    405414        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options) 
    406415        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results) 
    407         self._workspace.actionChain_Fitting.triggered.connect(self.actionChain_Fitting) 
    408416        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model) 
    409417        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model) 
     
    428436        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update) 
    429437 
     438        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput) 
     439 
    430440    #============ FILE ================= 
    431441    def actionLoadData(self): 
     
    527537        """ 
    528538        """ 
    529         print("actionShow_Grid_Window TRIGGERED") 
    530         pass 
     539        self.showBatchOutput(None) 
     540 
     541    def showBatchOutput(self, output_data): 
     542        """ 
     543        Display/redisplay the batch fit viewer 
     544        """ 
     545        if self.grid_window is None: 
     546            self.grid_window = BatchOutputPanel(parent=self, output_data=output_data) 
     547            subwindow = self._workspace.workspace.addSubWindow(self.grid_window) 
     548 
     549            #self.grid_window = BatchOutputPanel(parent=self, output_data=output_data) 
     550            self.grid_window.show() 
     551            return 
     552        if output_data: 
     553            self.grid_window.addFitResults(output_data) 
     554        self.grid_window.show() 
     555        if self.grid_window.windowState() == Qt.WindowMinimized: 
     556            self.grid_window.setWindowState(Qt.WindowActive) 
    531557 
    532558    def actionHide_Toolbar(self): 
     
    658684        """ 
    659685        print("actionFit_Results TRIGGERED") 
    660         pass 
    661  
    662     def actionChain_Fitting(self): 
    663         """ 
    664         """ 
    665         print("actionChain_Fitting TRIGGERED") 
    666686        pass 
    667687 
  • src/sas/qtgui/MainWindow/UI/MainWindowUI.ui

    r01ef3f7 rd4dac80  
    106106    <addaction name="actionFit_Results"/> 
    107107    <addaction name="separator"/> 
    108     <addaction name="actionChain_Fitting"/> 
    109     <addaction name="separator"/> 
    110108    <addaction name="actionAdd_Custom_Model"/> 
    111109    <addaction name="actionEdit_Custom_Model"/> 
  • src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py

    r8b480d27 rd4dac80  
    358358        elapsed = result[1] 
    359359 
    360         # ADD THE BATCH FIT VIEW HERE 
    361         # 
     360        if result is None: 
     361            msg = "Fitting failed." 
     362            self.parent.communicate.statusBarUpdateSignal.emit(msg) 
     363            return 
     364 
     365        # Show the grid panel 
     366        self.parent.communicate.sendDataToGridSignal.emit(result[0]) 
    362367 
    363368        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed) 
  • src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py

    rfde5bcd rd4dac80  
    421421        weight = numpy.abs(data) 
    422422    return weight 
     423 
     424def updateKernelWithResults(kernel, results): 
     425    """ 
     426    Takes model kernel and applies results dict to its parameters, 
     427    returning the modified (deep) copy of the kernel. 
     428    """ 
     429    assert(isinstance(results, dict)) 
     430    local_kernel = copy.deepcopy(kernel) 
     431 
     432    for parameter in results.keys(): 
     433        # Update the parameter value - note: this supports +/-inf as well 
     434        local_kernel.setParam(parameter, results[parameter][0]) 
     435 
     436    return local_kernel 
     437 
     438 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    rded5e77 rd4dac80  
    33from collections import defaultdict 
    44 
    5  
     5import copy 
    66import logging 
    77import traceback 
     
    2424import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    2525import sas.qtgui.Utilities.LocalConfig as LocalConfig 
    26 from sas.qtgui.Utilities.GridPanel import BatchOutputPanel 
    2726from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 
    2827from sas.qtgui.Plotting.PlotterData import Data1D 
     
    8887    fittingFinishedSignal = QtCore.pyqtSignal(tuple) 
    8988    batchFittingFinishedSignal = QtCore.pyqtSignal(tuple) 
     89    Calc1DFinishedSignal = QtCore.pyqtSignal(tuple) 
     90    Calc2DFinishedSignal = QtCore.pyqtSignal(tuple) 
    9091 
    9192    def __init__(self, parent=None, data=None, tab_id=1): 
     
    99100        self.tab_id = tab_id 
    100101 
    101         # Main Data[12]D holder 
    102         self.logic = FittingLogic() 
    103  
    104102        # Globals 
    105103        self.initializeGlobals() 
     
    107105        # Set up desired logging level 
    108106        logging.disable(LocalConfig.DISABLE_LOGGING) 
     107 
     108        # data index for the batch set 
     109        self.data_index = 0 
     110        # Main Data[12]D holders 
     111        # Logics.data contains a single Data1D/Data2D object 
     112        self._logic = [FittingLogic()] 
    109113 
    110114        # Main GUI setup up 
     
    134138        self.initializeControls() 
    135139 
    136         # Display HTML content 
    137         #self.setupHelp() 
     140        if data is not None: 
     141            self.data = data 
    138142 
    139143        # New font to display angstrom symbol 
     
    142146        self.label_19.setStyleSheet(new_font) 
    143147 
    144         self._index = None 
    145         if data is not None: 
    146             self.data = data 
     148    @property 
     149    def logic(self): 
     150        # make sure the logic contains at least one element 
     151        assert(self._logic) 
     152        # logic connected to the currently shown data 
     153        return self._logic[self.data_index] 
    147154 
    148155    @property 
     
    161168 
    162169        assert isinstance(value[0], QtGui.QStandardItem) 
    163         # _index contains the QIndex with data 
    164         self._index = value[0] 
    165170 
    166171        # Keep reference to all datasets for batch 
    167172        self.all_data = value 
    168173 
    169         # Update logics with data items 
     174        # Create logics with data items 
     175        self._logic=[] 
    170176        # Logics.data contains only a single Data1D/Data2D object 
    171         self.logic.data = GuiUtils.dataFromItem(value[0]) 
     177        for data_item in value: 
     178            self._logic.append(FittingLogic()) 
     179            self._logic[-1].data = GuiUtils.dataFromItem(data_item) 
    172180 
    173181        # Overwrite data type descriptor 
     
    226234        # Polydisp widget table default index for function combobox 
    227235        self.orig_poly_index = 3 
     236        # copy of current kernel model 
     237        self.kernel_module_copy = None 
    228238 
    229239        # Page id for fitting 
     
    488498        self.batchFittingFinishedSignal.connect(self.batchFitComplete) 
    489499        self.fittingFinishedSignal.connect(self.fitComplete) 
     500        self.Calc1DFinishedSignal.connect(self.complete1D) 
     501        self.Calc2DFinishedSignal.connect(self.complete2D) 
    490502 
    491503        # Signals from separate tabs asking for replot 
     
    930942        Update the logic based on the selected file in batch fitting 
    931943        """ 
    932         self._index = self.all_data[data_index] 
    933         self.logic.data = GuiUtils.dataFromItem(self.all_data[data_index]) 
     944        self.data_index = data_index 
    934945        self.updateQRange() 
    935946 
     
    12311242            return 
    12321243 
     1244        # keep local copy of kernel parameters, as they will change during the update 
     1245        self.kernel_module_copy = copy.deepcopy(self.kernel_module) 
     1246 
    12331247        # Create the fitting thread, based on the fitter 
    12341248        completefn = self.batchFittingCompleted if self.is_batch_fitting else self.fittingCompleted 
     
    12971311        #re-enable the Fit button 
    12981312        self.setFittingStopped() 
    1299         # Show the grid panel 
    1300         self.grid_window = BatchOutputPanel(parent=self, output_data=result[0]) 
    1301         self.grid_window.show() 
    1302  
    1303     def fittingCompleted(self, result): 
    1304         """ 
    1305         Send the finish message from calculate threads to main thread 
    1306         """ 
    1307         self.fittingFinishedSignal.emit(result) 
    1308  
    1309     def fitComplete(self, result): 
    1310         """ 
    1311         Receive and display fitting results 
    1312         "result" is a tuple of actual result list and the fit time in seconds 
    1313         """ 
    1314         #re-enable the Fit button 
    1315         self.setFittingStopped() 
    13161313 
    13171314        if result is None: 
     
    13201317            return 
    13211318 
     1319        # Show the grid panel 
     1320        self.communicate.sendDataToGridSignal.emit(result[0]) 
     1321 
     1322        elapsed = result[1] 
     1323        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed) 
     1324        self.communicate.statusBarUpdateSignal.emit(msg) 
     1325 
     1326        # Run over the list of results and update the items 
     1327        for res_index, res_list in enumerate(result[0]): 
     1328            # results 
     1329            res = res_list[0] 
     1330            param_dict = self.paramDictFromResults(res) 
     1331 
     1332            # create local kernel_module 
     1333            kernel_module = FittingUtilities.updateKernelWithResults(self.kernel_module, param_dict) 
     1334            # pull out current data 
     1335            data = self._logic[res_index].data 
     1336 
     1337            # Switch indexes 
     1338            self.onSelectBatchFilename(res_index) 
     1339 
     1340            method = self.complete1D if isinstance(self.data, Data1D) else self.complete2D 
     1341            self.calculateQGridForModelExt(data=data, model=kernel_module, completefn=method, use_threads=False) 
     1342 
     1343        # Restore original kernel_module, so subsequent fits on the same model don't pick up the new params 
     1344        if self.kernel_module is not None: 
     1345            self.kernel_module = copy.deepcopy(self.kernel_module_copy) 
     1346 
     1347    def paramDictFromResults(self, results): 
     1348        """ 
     1349        Given the fit results structure, pull out optimized parameters and return them as nicely 
     1350        formatted dict 
     1351        """ 
     1352        if results.fitness is None or \ 
     1353            not np.isfinite(results.fitness) or \ 
     1354            np.any(results.pvec is None) or \ 
     1355            not np.all(np.isfinite(results.pvec)): 
     1356            msg = "Fitting did not converge!" 
     1357            self.communicate.statusBarUpdateSignal.emit(msg) 
     1358            msg += results.mesg 
     1359            logging.error(msg) 
     1360            return 
     1361 
     1362        param_list = results.param_list # ['radius', 'radius.width'] 
     1363        param_values = results.pvec     # array([ 0.36221662,  0.0146783 ]) 
     1364        param_stderr = results.stderr   # array([ 1.71293015,  1.71294233]) 
     1365        params_and_errors = list(zip(param_values, param_stderr)) 
     1366        param_dict = dict(zip(param_list, params_and_errors)) 
     1367 
     1368        return param_dict 
     1369 
     1370    def fittingCompleted(self, result): 
     1371        """ 
     1372        Send the finish message from calculate threads to main thread 
     1373        """ 
     1374        self.fittingFinishedSignal.emit(result) 
     1375 
     1376    def fitComplete(self, result): 
     1377        """ 
     1378        Receive and display fitting results 
     1379        "result" is a tuple of actual result list and the fit time in seconds 
     1380        """ 
     1381        #re-enable the Fit button 
     1382        self.setFittingStopped() 
     1383 
     1384        if result is None: 
     1385            msg = "Fitting failed." 
     1386            self.communicate.statusBarUpdateSignal.emit(msg) 
     1387            return 
     1388 
    13221389        res_list = result[0][0] 
    13231390        res = res_list[0] 
    1324         if res.fitness is None or \ 
    1325             not np.isfinite(res.fitness) or \ 
    1326             np.any(res.pvec is None) or \ 
    1327             not np.all(np.isfinite(res.pvec)): 
    1328             msg = "Fitting did not converge!" 
    1329             self.communicate.statusBarUpdateSignal.emit(msg) 
    1330             msg += res.mesg 
    1331             logging.error(msg) 
    1332             return 
     1391        self.chi2 = res.fitness 
     1392        param_dict = self.paramDictFromResults(res) 
    13331393 
    13341394        elapsed = result[1] 
     
    13391399            msg = "Fitting completed successfully in: %s s." % GuiUtils.formatNumber(elapsed) 
    13401400        self.communicate.statusBarUpdateSignal.emit(msg) 
    1341  
    1342         self.chi2 = res.fitness 
    1343         param_list = res.param_list # ['radius', 'radius.width'] 
    1344         param_values = res.pvec     # array([ 0.36221662,  0.0146783 ]) 
    1345         param_stderr = res.stderr   # array([ 1.71293015,  1.71294233]) 
    1346         params_and_errors = list(zip(param_values, param_stderr)) 
    1347         param_dict = dict(zip(param_list, params_and_errors)) 
    13481401 
    13491402        # Dictionary of fitted parameter: value, error 
     
    20012054            fitted_data.symbol = 'Line' 
    20022055        # Notify the GUI manager so it can update the main model in DataExplorer 
    2003         GuiUtils.updateModelItemWithPlot(self._index, fitted_data, name) 
     2056        GuiUtils.updateModelItemWithPlot(self.all_data[self.data_index], fitted_data, name) 
    20042057 
    20052058    def createTheoryIndex(self, fitted_data): 
     
    20312084    def methodCompleteForData(self): 
    20322085        '''return the method for result parsin on calc complete ''' 
    2033         return self.complete1D if isinstance(self.data, Data1D) else self.complete2D 
    2034  
    2035     def calculateQGridForModel(self): 
    2036         """ 
    2037         Prepare the fitting data object, based on current ModelModel 
    2038         """ 
    2039         if self.kernel_module is None: 
    2040             return 
     2086        return self.completed1D if isinstance(self.data, Data1D) else self.completed2D 
     2087 
     2088    def calculateQGridForModelExt(self, data=None, model=None, completefn=None, use_threads=True): 
     2089        """ 
     2090        Wrapper for Calc1D/2D calls 
     2091        """ 
     2092        if data is None: 
     2093            data = self.data 
     2094        if model is None: 
     2095            model = self.kernel_module 
     2096        if completefn is None: 
     2097            completefn = self.methodCompleteForData() 
     2098 
    20412099        # Awful API to a backend method. 
    2042         method = self.methodCalculateForData()(data=self.data, 
    2043                                                model=self.kernel_module, 
     2100        calc_thread = self.methodCalculateForData()(data=data, 
     2101                                               model=model, 
    20442102                                               page_id=0, 
    20452103                                               qmin=self.q_range_min, 
     
    20502108                                               fid=None, 
    20512109                                               toggle_mode_on=False, 
    2052                                                completefn=None, 
     2110                                               completefn=completefn, 
    20532111                                               update_chisqr=True, 
    20542112                                               exception_handler=self.calcException, 
    20552113                                               source=None) 
    2056  
    2057         calc_thread = threads.deferToThread(method.compute) 
    2058         calc_thread.addCallback(self.methodCompleteForData()) 
    2059         calc_thread.addErrback(self.calculateDataFailed) 
     2114        if use_threads: 
     2115            if LocalConfig.USING_TWISTED: 
     2116                # start the thread with twisted 
     2117                thread = threads.deferToThread(calc_thread.compute) 
     2118                thread.addCallback(completefn) 
     2119                thread.addErrback(self.calculateDataFailed) 
     2120            else: 
     2121                # Use the old python threads + Queue 
     2122                calc_thread.queue() 
     2123                calc_thread.ready(2.5) 
     2124        else: 
     2125            results = calc_thread.compute() 
     2126            completefn(results) 
     2127 
     2128    def calculateQGridForModel(self): 
     2129        """ 
     2130        Prepare the fitting data object, based on current ModelModel 
     2131        """ 
     2132        if self.kernel_module is None: 
     2133            return 
     2134        self.calculateQGridForModelExt() 
    20602135 
    20612136    def calculateDataFailed(self, reason): 
     
    20642139        """ 
    20652140        print("Calculate Data failed with ", reason) 
     2141 
     2142    def completed1D(self, return_data): 
     2143        self.Calc1DFinishedSignal.emit(return_data) 
     2144 
     2145    def completed2D(self, return_data): 
     2146        self.Calc2DFinishedSignal.emit(return_data) 
    20662147 
    20672148    def complete1D(self, return_data): 
     
    21072188        residuals_plot.id = "Residual " + residuals_plot.id 
    21082189        self.createNewIndex(residuals_plot) 
    2109         #self.communicate.plotUpdateSignal.emit([residuals_plot]) 
    21102190 
    21112191    def calcException(self, etype, value, tb): 
  • src/sas/qtgui/Perspectives/Fitting/ModelThread.py

    rcee5c78 rd4dac80  
    88from sas.sascalc.data_util.calcthread import CalcThread 
    99from sas.sascalc.fit.MultiplicationModel import MultiplicationModel 
     10import sas.qtgui.Utilities.LocalConfig as LocalConfig 
    1011 
    1112class Calc2D(CalcThread): 
     
    99100        output[index_model] = value 
    100101        elapsed = time.time() - self.starttime 
    101         #self.complete(image=output, 
    102         #               data=self.data, 
    103         #               page_id=self.page_id, 
    104         #               model=self.model, 
    105         #               state=self.state, 
    106         #               toggle_mode_on=self.toggle_mode_on, 
    107         #               elapsed=elapsed, 
    108         #               index=index_model, 
    109         #               fid=self.fid, 
    110         #               qmin=self.qmin, 
    111         #               qmax=self.qmax, 
    112         #               weight=self.weight, 
    113         #               #qstep=self.qstep, 
    114         #               update_chisqr=self.update_chisqr, 
    115         #               source=self.source) 
    116         return (output, 
    117                 self.data, 
    118                 self.page_id, 
    119                 self.model, 
    120                 self.state, 
    121                 self.toggle_mode_on, 
    122                 elapsed, 
    123                 index_model, 
    124                 self.fid, 
    125                 self.qmin, 
    126                 self.qmax, 
    127                 self.weight, 
    128                 self.update_chisqr, 
    129                 self.source) 
     102 
     103        if LocalConfig.USING_TWISTED: 
     104            return (output, 
     105                    self.data, 
     106                    self.page_id, 
     107                    self.model, 
     108                    self.state, 
     109                    self.toggle_mode_on, 
     110                    elapsed, 
     111                    index_model, 
     112                    self.fid, 
     113                    self.qmin, 
     114                    self.qmax, 
     115                    self.weight, 
     116                    self.update_chisqr, 
     117                    self.source) 
     118        else: 
     119            self.complete(image=output, 
     120                           data=self.data, 
     121                           page_id=self.page_id, 
     122                           model=self.model, 
     123                           state=self.state, 
     124                           toggle_mode_on=self.toggle_mode_on, 
     125                           elapsed=elapsed, 
     126                           index=index_model, 
     127                           fid=self.fid, 
     128                           qmin=self.qmin, 
     129                           qmax=self.qmax, 
     130                           weight=self.weight, 
     131                           #qstep=self.qstep, 
     132                           update_chisqr=self.update_chisqr, 
     133                           source=self.source) 
    130134 
    131135 
     
    229233        elapsed = time.time() - self.starttime 
    230234 
    231         return (self.data.x[index], output[index], 
    232                 self.page_id, 
    233                 self.state, 
    234                 self.weight, 
    235                 self.fid, 
    236                 self.toggle_mode_on, 
    237                 elapsed, index, self.model, 
    238                 self.data, 
    239                 self.update_chisqr, 
    240                 self.source) 
    241  
    242         # TODO: as of 4.1, the output contains more items: 
    243         # unsmeared_* and pq_model/sq_model 
    244         # Need to add these too 
    245  
    246         #self.complete(x=self.data.x[index], y=output[index], 
    247         #              page_id=self.page_id, 
    248         #              state=self.state, 
    249         #              weight=self.weight, 
    250         #              fid=self.fid, 
    251         #              toggle_mode_on=self.toggle_mode_on, 
    252         #              elapsed=elapsed, index=index, model=self.model, 
    253         #              data=self.data, 
    254         #              update_chisqr=self.update_chisqr, 
    255         #              source=self.source, 
    256         #              unsmeared_model=unsmeared_output, 
    257         #              unsmeared_data=unsmeared_data, 
    258         #              unsmeared_error=unsmeared_error, 
    259         #              pq_model=pq_values, 
    260         #              sq_model=sq_values) 
     235        if LocalConfig.USING_TWISTED: 
     236            return (self.data.x[index], output[index], 
     237                    self.page_id, 
     238                    self.state, 
     239                    self.weight, 
     240                    self.fid, 
     241                    self.toggle_mode_on, 
     242                    elapsed, index, self.model, 
     243                    self.data, 
     244                    self.update_chisqr, 
     245                    self.source) 
     246        else: 
     247            self.complete(x=self.data.x[index], y=output[index], 
     248                          page_id=self.page_id, 
     249                          state=self.state, 
     250                          weight=self.weight, 
     251                          fid=self.fid, 
     252                          toggle_mode_on=self.toggle_mode_on, 
     253                          elapsed=elapsed, index=index, model=self.model, 
     254                          data=self.data, 
     255                          update_chisqr=self.update_chisqr, 
     256                          source=self.source, 
     257                          unsmeared_model=unsmeared_output, 
     258                          unsmeared_data=unsmeared_data, 
     259                          unsmeared_error=unsmeared_error, 
     260                          pq_model=pq_values, 
     261                          sq_model=sq_values) 
    261262 
    262263    def results(self): 
  • src/sas/qtgui/Perspectives/Fitting/ModelUtilities.py

    r3b3b40b rd4dac80  
    173173        return {} 
    174174 
    175     plugin_log("looking for models in: %s" % str(directory)) 
    176     # compile_file(directory)  #always recompile the folder plugin 
    177     logging.info("plugin model dir: %s" % str(directory)) 
    178  
    179175    plugins = {} 
    180176    for filename in os.listdir(directory): 
  • src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui

    r3b3b40b rd4dac80  
    216216            </property> 
    217217            <property name="toolTip"> 
    218              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Switch on magnetic scattering parameters.&lt;/p&gt;&lt;p&gt;This option is available only for 2D models.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     218             <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Switch on Chain Fitting (parameter reuse) for batch datasets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
    219219            </property> 
    220220            <property name="text"> 
  • src/sas/qtgui/Utilities/GridPanel.py

    r8b480d27 rd4dac80  
    11import os 
     2import sys 
    23import time 
    34import logging 
    45import webbrowser 
    56 
    6 from PyQt5 import QtCore, QtWidgets 
     7from PyQt5 import QtCore, QtWidgets, QtGui 
    78 
    89import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    9  
     10from sas.qtgui.Plotting.PlotterData import Data1D 
    1011from sas.qtgui.Utilities.UI.GridPanelUI import Ui_GridPanelUI 
    1112 
     
    1516    Class for stateless grid-like printout of model parameters for mutiple models 
    1617    """ 
     18    ERROR_COLUMN_CAPTION = " (Err)" 
     19    IS_WIN = (sys.platform == 'win32') 
    1720    def __init__(self, parent = None, output_data=None): 
    1821 
    19         super(BatchOutputPanel, self).__init__() 
     22        super(BatchOutputPanel, self).__init__(parent._parent) 
    2023        self.setupUi(self) 
    2124 
    22         self.data = output_data 
    2325        self.parent = parent 
    2426        if hasattr(self.parent, "communicate"): 
     
    3032        self.grid_filename = "" 
    3133 
     34        self.has_data = False if output_data is None else True 
     35        # Tab numbering 
     36        self.tab_number = 1 
     37 
     38        # System dependent menu items 
     39        if not self.IS_WIN: 
     40            self.actionOpen_with_Excel.setVisible(False) 
     41 
     42        # list of QTableWidgets, indexed by tab number 
     43        self.tables = [] 
     44        self.tables.append(self.tblParams) 
     45 
    3246        # context menu on the table 
    3347        self.tblParams.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 
    3448        self.tblParams.customContextMenuRequested.connect(self.showContextMenu) 
    3549 
    36         # Fill in the table from input data 
    37         self.setupTable(output_data) 
    38  
    3950        # Command buttons 
    4051        self.cmdHelp.clicked.connect(self.onHelp) 
     52        self.cmdPlot.clicked.connect(self.onPlot) 
     53 
     54        # Fill in the table from input data 
     55        self.setupTable(widget=self.tblParams, data=output_data) 
     56        if output_data is not None: 
     57            # Set a table tooltip describing the model 
     58            model_name = output_data[0][0].model.id 
     59            self.tabWidget.setTabToolTip(0, model_name) 
     60 
     61    def closeEvent(self, event): 
     62        """ 
     63        Overwrite QDialog close method to allow for custom widget close 
     64        """ 
     65        # Maybe we should just minimize 
     66        self.setWindowState(QtCore.Qt.WindowMinimized) 
     67        event.ignore() 
    4168 
    4269    def addToolbarActions(self): 
     
    6491 
    6592        self.setupTableFromCSV(lines) 
     93        self.has_data = True 
     94 
     95    def currentTable(self): 
     96        """ 
     97        Returns the currently shown QTabWidget 
     98        """ 
     99        return self.tables[self.tabWidget.currentIndex()] 
    66100 
    67101    def showContextMenu(self, position): 
     
    70104        """ 
    71105        menu = QtWidgets.QMenu() 
    72         rows = [s.row() for s in self.tblParams.selectionModel().selectedRows()] 
     106        rows = [s.row() for s in self.currentTable().selectionModel().selectedRows()] 
    73107        num_rows = len(rows) 
    74108        if num_rows <= 0: 
     
    84118 
    85119        # Define the callbacks 
    86         self.actionPlotResults.triggered.connect(self.plotFits) 
     120        self.actionPlotResults.triggered.connect(self.onPlot) 
    87121        try: 
    88             menu.exec_(self.tblParams.viewport().mapToGlobal(position)) 
     122            menu.exec_(self.currentTable().viewport().mapToGlobal(position)) 
    89123        except AttributeError as ex: 
    90124            logging.error("Error generating context menu: %s" % ex) 
    91125        return 
     126 
     127    def addTabPage(self): 
     128        """ 
     129        Add new tab page with QTableWidget 
     130        """ 
     131        layout = QtWidgets.QVBoxLayout() 
     132        tab_widget = QtWidgets.QTableWidget(parent=self) 
     133        # Same behaviour as the original tblParams 
     134        tab_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 
     135        tab_widget.setAlternatingRowColors(True) 
     136        tab_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 
     137        tab_widget.setLayout(layout) 
     138        # Simple naming here. 
     139        # One would think naming the tab with current model name would be good. 
     140        # However, some models have LONG names, which doesn't look well on the tab bar. 
     141        self.tab_number += 1 
     142        tab_name = "Tab " + str(self.tab_number) 
     143        # each table needs separate slots. 
     144        tab_widget.customContextMenuRequested.connect(self.showContextMenu) 
     145        self.tables.append(tab_widget) 
     146        self.tabWidget.addTab(tab_widget, tab_name) 
     147        # Make the new tab active 
     148        self.tabWidget.setCurrentIndex(self.tab_number-1) 
     149 
     150    def addFitResults(self, results): 
     151        """ 
     152        Create a new tab with batch fitting results 
     153        """ 
     154        self.addTabPage() 
     155        # Update the new widget 
     156        # Fill in the table from input data in the last/newest page 
     157        assert(self.tables) 
     158        self.setupTable(widget=self.tables[-1], data=results) 
     159        self.has_data = True 
     160 
     161        # Set a table tooltip describing the model 
     162        model_name = results[0][0].model.id 
     163        self.tabWidget.setTabToolTip(self.tabWidget.count()-1, model_name) 
     164 
    92165 
    93166    @classmethod 
     
    103176            logging.warning("Cannot display help. %s" % ex) 
    104177 
    105     def plotFits(self): 
     178    def onPlot(self): 
    106179        """ 
    107180        Plot selected fits by sending signal to the parent 
    108181        """ 
    109         rows = [s.row() for s in self.tblParams.selectionModel().selectedRows()] 
    110         data = self.dataFromTable(self.tblParams) 
     182        rows = [s.row() for s in self.currentTable().selectionModel().selectedRows()] 
     183        if not rows: 
     184            msg = "Nothing to plot!" 
     185            self.parent.communicate.statusBarUpdateSignal.emit(msg) 
     186            return 
     187        data = self.dataFromTable(self.currentTable()) 
    111188        # data['Data'] -> ['filename1', 'filename2', ...] 
    112189        # look for the 'Data' column and extract the filename 
     
    141218            tmpfile = tempfile.NamedTemporaryFile(delete=False, mode="w+", suffix=".csv") 
    142219            self.grid_filename = tmpfile.name 
    143             data = self.dataFromTable(self.tblParams) 
     220            data = self.dataFromTable(self.currentTable()) 
    144221            t = time.localtime(time.time()) 
    145222            time_str = time.strftime("%b %d %H:%M of %Y", t) 
     
    181258        if not filename: 
    182259            return 
    183         data = self.dataFromTable(self.tblParams) 
     260        data = self.dataFromTable(self.currentTable()) 
    184261        details = "File generated by SasView\n" 
    185262        with open(filename, 'w') as csv_file: 
     
    190267        Create tablewidget items and show them, based on params 
    191268        """ 
    192         # Clear existing display 
    193         self.tblParams.clear() 
     269        # Is this an empty grid? 
     270        if self.has_data: 
     271            # Add a new page 
     272            self.addTabPage() 
     273            # Access the newly created QTableWidget 
     274            current_page = self.tables[-1] 
     275        else: 
     276            current_page = self.tblParams 
    194277        # headers 
    195278        param_list = csv_data[1].rstrip().split(',') 
     279        # need to remove the 2 header rows to get the total data row number 
     280        rows = len(csv_data) -2 
     281        assert(rows > -1) 
     282        columns = len(param_list) 
     283        current_page.setColumnCount(columns) 
     284        current_page.setRowCount(rows) 
     285 
    196286        for i, param in enumerate(param_list): 
    197             self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 
     287            current_page.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 
    198288 
    199289        # first - Chi2 and data filename 
    200290        for i_row, row in enumerate(csv_data[2:]): 
    201291            for i_col, col in enumerate(row.rstrip().split(',')): 
    202                 self.tblParams.setItem(i_row, i_col, QtWidgets.QTableWidgetItem(col)) 
    203  
    204         self.tblParams.resizeColumnsToContents() 
    205  
    206     def setupTable(self, data): 
     292                current_page.setItem(i_row, i_col, QtWidgets.QTableWidgetItem(col)) 
     293 
     294        current_page.resizeColumnsToContents() 
     295 
     296    def setupTable(self, widget=None, data=None): 
    207297        """ 
    208298        Create tablewidget items and show them, based on params 
    209299        """ 
    210         # headers 
     300        # quietly leave is nothing to show 
     301        if data is None or widget is None: 
     302            return 
     303 
     304        # Figure out the headers 
    211305        model = data[0][0] 
     306 
     307        # TODO: add a conditional for magnetic models 
    212308        param_list = [m for m in model.model.params.keys() if ":" not in m] 
    213309 
    214310        # Check if 2D model. If not, remove theta/phi 
     311        if isinstance(model.data.sas_data, Data1D): 
     312            param_list.remove('theta') 
     313            param_list.remove('phi') 
    215314 
    216315        rows = len(data) 
    217316        columns = len(param_list) 
    218         self.tblParams.setColumnCount(columns+2) 
    219         self.tblParams.setRowCount(rows) 
    220  
     317 
     318        widget.setColumnCount(columns+2) # add 2 initial columns defined below 
     319        widget.setRowCount(rows) 
     320 
     321        # Insert two additional columns 
    221322        param_list.insert(0, "Data") 
    222323        param_list.insert(0, "Chi2") 
    223324        for i, param in enumerate(param_list): 
    224             self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 
    225  
     325            widget.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 
     326 
     327        # dictionary of parameter errors for post-processing 
     328        # [param_name] = [param_column_nr, error_for_row_1, error_for_row_2,...] 
     329        error_columns = {} 
    226330        # first - Chi2 and data filename 
    227331        for i_row, row in enumerate(data): 
     
    231335            if hasattr(row[0].data, "sas_data"): 
    232336                filename = row[0].data.sas_data.filename 
    233             self.tblParams.setItem(i_row, 0, QtWidgets.QTableWidgetItem(GuiUtils.formatNumber(chi2, high=True))) 
    234             self.tblParams.setItem(i_row, 1, QtWidgets.QTableWidgetItem(str(filename))) 
     337            widget.setItem(i_row, 0, QtWidgets.QTableWidgetItem(GuiUtils.formatNumber(chi2, high=True))) 
     338            widget.setItem(i_row, 1, QtWidgets.QTableWidgetItem(str(filename))) 
    235339            # Now, all the parameters 
    236340            for i_col, param in enumerate(param_list[2:]): 
     
    238342                    # parameter is on the to-optimize list - get the optimized value 
    239343                    par_value = row[0].pvec[row[0].param_list.index(param)] 
    240                     # should we parse out errors here and store them? 
     344                    # parse out errors and store them for later use 
     345                    err_value = row[0].stderr[row[0].param_list.index(param)] 
     346                    if param in error_columns: 
     347                        error_columns[param].append(err_value) 
     348                    else: 
     349                        error_columns[param] = [i_col, err_value] 
    241350                else: 
    242351                    # parameter was not varied 
    243352                    par_value = row[0].model.params[param] 
    244                 self.tblParams.setItem(i_row, i_col+2, QtWidgets.QTableWidgetItem( 
     353 
     354                widget.setItem(i_row, i_col+2, QtWidgets.QTableWidgetItem( 
    245355                    GuiUtils.formatNumber(par_value, high=True))) 
    246356 
    247         self.tblParams.resizeColumnsToContents() 
     357        # Add errors 
     358        error_list = list(error_columns.keys()) 
     359        for error_param in error_list[::-1]: # must be reverse to keep column indices 
     360            # the offset for the err column: +2 from the first two extra columns, +1 to append this column 
     361            error_column = error_columns[error_param][0]+3 
     362            error_values = error_columns[error_param][1:] 
     363            widget.insertColumn(error_column) 
     364 
     365            column_name = error_param + self.ERROR_COLUMN_CAPTION 
     366            widget.setHorizontalHeaderItem(error_column, QtWidgets.QTableWidgetItem(column_name)) 
     367 
     368            for i_row, error in enumerate(error_values): 
     369                item = QtWidgets.QTableWidgetItem(GuiUtils.formatNumber(error, high=True)) 
     370                # Fancy, italic font for errors 
     371                font = QtGui.QFont() 
     372                font.setItalic(True) 
     373                item.setFont(font) 
     374                widget.setItem(i_row, error_column, item) 
     375 
     376        # resize content 
     377        widget.resizeColumnsToContents() 
    248378 
    249379    @classmethod 
  • src/sas/qtgui/Utilities/GuiUtils.py

    r27689dc rd4dac80  
    245245    customModelDirectoryChanged = QtCore.pyqtSignal() 
    246246 
     247    # Notify the gui manager about new data to be added to the grid view 
     248    sendDataToGridSignal = QtCore.pyqtSignal(list) 
     249 
     250 
    247251def updateModelItemWithPlot(item, update_data, name=""): 
    248252    """ 
     
    333337    """ 
    334338    assert isinstance(item, QtGui.QStandardItem) 
    335     #assert isinstance(update_data, list) 
    336339 
    337340    # Add the actual Data1D/Data2D object 
     
    465468 
    466469    return info_item 
     470 
     471def dataFromItem(item): 
     472    """ 
     473    Retrieve Data1D/2D component from QStandardItem. 
     474    The assumption - data stored in SasView standard, in child 0 
     475    """ 
     476    return item.child(0).data() 
    467477 
    468478def openLink(url): 
     
    813823    return (xLabel, yLabel, xscale, yscale) 
    814824 
    815 def dataFromItem(item): 
    816     """ 
    817     Retrieve Data1D/2D component from QStandardItem. 
    818     The assumption - data stored in SasView standard, in child 0 
    819     """ 
    820     return item.child(0).data() 
    821  
    822825def formatNumber(value, high=False): 
    823826    """ 
  • src/sas/qtgui/Utilities/LocalConfig.py

    r3b3b40b rd4dac80  
    134134 
    135135# Default threading model 
    136 USING_TWISTED = False 
     136USING_TWISTED = True 
    137137 
    138138# Logging levels to disable, if any 
  • src/sas/qtgui/Utilities/TabbedModelEditor.py

    r93c79b5 rd4dac80  
    3535        self.edit_only = edit_only 
    3636        self.is_modified = False 
     37        self.label = None 
    3738 
    3839        self.addWidgets() 
     
    240241        # Run the model test in sasmodels 
    241242        try: 
    242             _ = GuiUtils.checkModel(full_path) 
     243            model_results = self.checkModel(full_path) 
     244            logging.info(model_results) 
    243245        except Exception as ex: 
    244246            msg = "Error building model: "+ str(ex) 
  • src/sas/qtgui/Utilities/UI/GridPanelUI.ui

    r3b3b40b rd4dac80  
    88    <y>0</y> 
    99    <width>939</width> 
    10     <height>329</height> 
     10    <height>330</height> 
    1111   </rect> 
    1212  </property> 
     
    2121  </property> 
    2222  <widget class="QWidget" name="centralwidget"> 
    23    <layout class="QGridLayout" name="gridLayout"> 
     23   <layout class="QGridLayout" name="gridLayout_2"> 
     24    <item row="0" column="0"> 
     25     <widget class="QTabWidget" name="tabWidget"> 
     26      <property name="tabPosition"> 
     27       <enum>QTabWidget::South</enum> 
     28      </property> 
     29      <property name="currentIndex"> 
     30       <number>0</number> 
     31      </property> 
     32      <widget class="QWidget" name="tab"> 
     33       <attribute name="title"> 
     34        <string>Tab 1</string> 
     35       </attribute> 
     36       <layout class="QGridLayout" name="gridLayout"> 
     37        <item row="0" column="0"> 
     38         <widget class="QTableWidget" name="tblParams"> 
     39          <property name="contextMenuPolicy"> 
     40           <enum>Qt::CustomContextMenu</enum> 
     41          </property> 
     42          <property name="alternatingRowColors"> 
     43           <bool>true</bool> 
     44          </property> 
     45          <property name="selectionBehavior"> 
     46           <enum>QAbstractItemView::SelectRows</enum> 
     47          </property> 
     48         </widget> 
     49        </item> 
     50       </layout> 
     51      </widget> 
     52     </widget> 
     53    </item> 
    2454    <item row="1" column="0"> 
    2555     <layout class="QHBoxLayout" name="horizontalLayout"> 
     
    3666        </property> 
    3767       </spacer> 
     68      </item> 
     69      <item> 
     70       <widget class="QPushButton" name="cmdPlot"> 
     71        <property name="text"> 
     72         <string>Plot</string> 
     73        </property> 
     74       </widget> 
    3875      </item> 
    3976      <item> 
     
    5390     </layout> 
    5491    </item> 
    55     <item row="0" column="0"> 
    56      <widget class="QTableWidget" name="tblParams"> 
    57       <property name="contextMenuPolicy"> 
    58        <enum>Qt::CustomContextMenu</enum> 
    59       </property> 
    60       <property name="alternatingRowColors"> 
    61        <bool>true</bool> 
    62       </property> 
    63       <property name="selectionBehavior"> 
    64        <enum>QAbstractItemView::SelectRows</enum> 
    65       </property> 
    66      </widget> 
    67     </item> 
    6892   </layout> 
    6993  </widget> 
     
    7397     <x>0</x> 
    7498     <y>0</y> 
    75      <width>939</width> 
     99     <width>510</width> 
    76100     <height>26</height> 
    77101    </rect> 
  • src/sas/qtgui/Utilities/UnitTesting/GridPanelTest.py

    r3b3b40b rd4dac80  
    7070        self.assertIsInstance(self.widget, QtWidgets.QMainWindow) 
    7171        # Default title 
    72         self.assertEqual(self.widget.windowTitle(), "Grid Panel") 
     72        self.assertEqual(self.widget.windowTitle(), "Batch Fitting Results") 
    7373 
    7474        # non-modal window 
Note: See TracChangeset for help on using the changeset viewer.