Ignore:
Timestamp:
May 7, 2018 6:47:21 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:
42787fb
Parents:
b0ba43e (diff), 80468f6 (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_Pr

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/Utilities/GridPanel.py

    r5a5e371 r3c6ecd9  
    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 
    92126 
     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 
     165 
    93166    @classmethod 
    94167    def onHelp(cls): 
     
    97170        """ 
    98171        location = GuiUtils.HELP_DIRECTORY_LOCATION 
    99         url = "/user/sasgui/perspectives/fitting/fitting_help.html#batch-fit-mode" 
     172        url = "/user/qtgui/Perspectives/Fitting/fitting_help.html#batch-fit-mode" 
    100173        try: 
    101174            webbrowser.open('file://' + os.path.realpath(location+url)) 
     
    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 
Note: See TracChangeset for help on using the changeset viewer.