source: sasview/src/sas/qtgui/Utilities/GridPanel.py @ ae34d30

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since ae34d30 was ae34d30, checked in by krzywon, 6 years ago

Dmax close events inform P(r) window, data removal removes batch results for that data set, and general code cleanup.

  • Property mode set to 100644
File size: 12.9 KB
RevLine 
[3b3b40b]1import os
2import time
3import logging
4import webbrowser
5
[8b480d27]6from PyQt5 import QtCore, QtWidgets
[3b3b40b]7
8import sas.qtgui.Utilities.GuiUtils as GuiUtils
9
10from sas.qtgui.Utilities.UI.GridPanelUI import Ui_GridPanelUI
11
12
13class BatchOutputPanel(QtWidgets.QMainWindow, Ui_GridPanelUI):
14    """
15    Class for stateless grid-like printout of model parameters for mutiple models
16    """
17    def __init__(self, parent = None, output_data=None):
18
19        super(BatchOutputPanel, self).__init__()
20        self.setupUi(self)
21
22        self.data = output_data
23        self.parent = parent
24        if hasattr(self.parent, "communicate"):
25            self.communicate = parent.communicate
26
27        self.addToolbarActions()
28
29        # file name for the dataset
30        self.grid_filename = ""
31
32        # context menu on the table
33        self.tblParams.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
34        self.tblParams.customContextMenuRequested.connect(self.showContextMenu)
35
36        # Fill in the table from input data
37        self.setupTable(output_data)
38
39        # Command buttons
40        self.cmdHelp.clicked.connect(self.onHelp)
41
42    def addToolbarActions(self):
43        """
44        Assing actions and callbacks to the File menu items
45        """
46        self.actionOpen.triggered.connect(self.actionLoadData)
47        self.actionOpen_with_Excel.triggered.connect(self.actionSendToExcel)
48        self.actionSave.triggered.connect(self.actionSaveFile)
49
50    def actionLoadData(self):
51        """
52        Open file load dialog and load a .csv file
53        """
54        datafile = QtWidgets.QFileDialog.getOpenFileName(
55            self, "Choose a file with results", "", "CSV files (*.csv)", None,
56            QtWidgets.QFileDialog.DontUseNativeDialog)[0]
57
58        if not datafile:
59            logging.info("No data file chosen.")
60            return
61
62        with open(datafile, 'r') as csv_file:
63            lines = csv_file.readlines()
64
65        self.setupTableFromCSV(lines)
66
67    def showContextMenu(self, position):
68        """
69        Show context specific menu in the tab table widget.
70        """
71        menu = QtWidgets.QMenu()
72        rows = [s.row() for s in self.tblParams.selectionModel().selectedRows()]
73        num_rows = len(rows)
74        if num_rows <= 0:
75            return
76        # Find out which items got selected and in which row
77        # Select for fitting
78
79        self.actionPlotResults = QtWidgets.QAction(self)
80        self.actionPlotResults.setObjectName("actionPlot")
81        self.actionPlotResults.setText(QtCore.QCoreApplication.translate("self", "Plot selected fits."))
82
83        menu.addAction(self.actionPlotResults)
84
85        # Define the callbacks
86        self.actionPlotResults.triggered.connect(self.plotFits)
87        try:
88            menu.exec_(self.tblParams.viewport().mapToGlobal(position))
89        except AttributeError as ex:
90            logging.error("Error generating context menu: %s" % ex)
91        return
92
[8b480d27]93    @classmethod
94    def onHelp(cls):
[3b3b40b]95        """
96        Open a local url in the default browser
97        """
98        location = GuiUtils.HELP_DIRECTORY_LOCATION
99        url = "/user/sasgui/perspectives/fitting/fitting_help.html#batch-fit-mode"
100        try:
101            webbrowser.open('file://' + os.path.realpath(location+url))
102        except webbrowser.Error as ex:
103            logging.warning("Cannot display help. %s" % ex)
104
105    def plotFits(self):
106        """
107        Plot selected fits by sending signal to the parent
108        """
109        rows = [s.row() for s in self.tblParams.selectionModel().selectedRows()]
110        data = self.dataFromTable(self.tblParams)
111        # data['Data'] -> ['filename1', 'filename2', ...]
112        # look for the 'Data' column and extract the filename
113        for row in rows:
114            try:
115                filename = data['Data'][row]
116                # emit a signal so the plots are being shown
117                self.communicate.plotFromFilenameSignal.emit(filename)
118            except (IndexError, AttributeError):
119                # data messed up.
120                return
121
[8b480d27]122    @classmethod
123    def dataFromTable(cls, table):
[3b3b40b]124        """
125        Creates a dictionary {<parameter>:[list of values]} from the parameter table
126        """
127        assert(isinstance(table, QtWidgets.QTableWidget))
128        params = {}
129        for column in range(table.columnCount()):
130            value = [table.item(row, column).data(0) for row in range(table.rowCount())]
131            key = table.horizontalHeaderItem(column).data(0)
132            params[key] = value
133        return params
134
135    def actionSendToExcel(self):
136        """
137        Generates a .csv file and opens the default CSV reader
138        """
139        if not self.grid_filename:
140            import tempfile
141            tmpfile = tempfile.NamedTemporaryFile(delete=False, mode="w+", suffix=".csv")
142            self.grid_filename = tmpfile.name
143            data = self.dataFromTable(self.tblParams)
144            t = time.localtime(time.time())
145            time_str = time.strftime("%b %d %H:%M of %Y", t)
[8b480d27]146            details = "File Generated by SasView "
[3b3b40b]147            details += "on %s.\n" % time_str
148            self.writeBatchToFile(data=data, tmpfile=tmpfile, details=details)
149            tmpfile.close()
150
151        try:
152            from win32com.client import Dispatch
153            excel_app = Dispatch('Excel.Application')
154            excel_app.Workbooks.Open(self.grid_filename)
155            excel_app.Visible = 1
156        except Exception as ex:
157            msg = "Error occured when calling Excel.\n"
158            msg += ex
159            self.parent.communicate.statusBarUpdateSignal.emit(msg)
160
161    def actionSaveFile(self):
162        """
163        Generate a .csv file and dump it do disk
164        """
165        t = time.localtime(time.time())
166        time_str = time.strftime("%b %d %H %M of %Y", t)
167        default_name = "Batch_Fitting_"+time_str+".csv"
168
169        wildcard = "CSV files (*.csv);;"
170        kwargs = {
171            'caption'   : 'Save As',
172            'directory' : default_name,
173            'filter'    : wildcard,
174            'parent'    : None,
175        }
176        # Query user for filename.
177        filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
178        filename = filename_tuple[0]
179
180        # User cancelled.
181        if not filename:
182            return
183        data = self.dataFromTable(self.tblParams)
184        details = "File generated by SasView\n"
185        with open(filename, 'w') as csv_file:
186            self.writeBatchToFile(data=data, tmpfile=csv_file, details=details)
187
188    def setupTableFromCSV(self, csv_data):
189        """
190        Create tablewidget items and show them, based on params
191        """
192        # Clear existing display
193        self.tblParams.clear()
194        # headers
195        param_list = csv_data[1].rstrip().split(',')
196        for i, param in enumerate(param_list):
197            self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param))
198
199        # first - Chi2 and data filename
200        for i_row, row in enumerate(csv_data[2:]):
201            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):
207        """
208        Create tablewidget items and show them, based on params
209        """
210        # headers
211        model = data[0][0]
212        param_list = [m for m in model.model.params.keys() if ":" not in m]
213
214        # Check if 2D model. If not, remove theta/phi
215
216        rows = len(data)
217        columns = len(param_list)
218        self.tblParams.setColumnCount(columns+2)
219        self.tblParams.setRowCount(rows)
220
221        param_list.insert(0, "Data")
222        param_list.insert(0, "Chi2")
223        for i, param in enumerate(param_list):
224            self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param))
225
226        # first - Chi2 and data filename
227        for i_row, row in enumerate(data):
228            # each row corresponds to a single fit
229            chi2 = row[0].fitness
230            filename = ""
231            if hasattr(row[0].data, "sas_data"):
232                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)))
235            # Now, all the parameters
236            for i_col, param in enumerate(param_list[2:]):
237                if param in row[0].param_list:
238                    # parameter is on the to-optimize list - get the optimized value
239                    par_value = row[0].pvec[row[0].param_list.index(param)]
240                    # should we parse out errors here and store them?
241                else:
242                    # parameter was not varied
243                    par_value = row[0].model.params[param]
244                self.tblParams.setItem(i_row, i_col+2, QtWidgets.QTableWidgetItem(
245                    GuiUtils.formatNumber(par_value, high=True)))
246
247        self.tblParams.resizeColumnsToContents()
248
[8b480d27]249    @classmethod
250    def writeBatchToFile(cls, data, tmpfile, details=""):
[3b3b40b]251        """
252        Helper to write result from batch into cvs file
253        """
254        name = tmpfile.name
255        if data is None or name is None or name.strip() == "":
256            return
257        _, ext = os.path.splitext(name)
258        separator = "\t"
259        if ext.lower() == ".csv":
260            separator = ","
261        tmpfile.write(details)
262        for col_name in data.keys():
263            tmpfile.write(col_name)
264            tmpfile.write(separator)
265        tmpfile.write('\n')
266        max_list = [len(value) for value in data.values()]
267        if len(max_list) == 0:
268            return
269        max_index = max(max_list)
270        index = 0
271        while index < max_index:
272            for value_list in data.values():
273                if index < len(value_list):
274                    tmpfile.write(str(value_list[index]))
275                    tmpfile.write(separator)
276                else:
277                    tmpfile.write('')
278                    tmpfile.write(separator)
279            tmpfile.write('\n')
280            index += 1
281
[effdd98]282
283class BatchInversionOutputPanel(BatchOutputPanel):
284    """
285        Class for stateless grid-like printout of P(r) parameters for any number
286        of data sets
287    """
288    def __init__(self, parent = None, output_data=None):
289
290        super(BatchInversionOutputPanel, self).__init__(parent, output_data)
291
292    def setupTable(self, data):
293        """
294        Create tablewidget items and show them, based on params
295        """
296        # headers
[76567bb]297        param_list = ['Filename', 'Rg [Å]', 'χ^2/dof', 'I(Q=0)', 'Oscillations',
298                      'Background [Å^-1]', 'P+ Fraction', 'P+1-σ Fraction',
299                      'Calc. Time [sec]']
[effdd98]300
301        keys = data.keys()
302        rows = len(keys)
303        columns = len(param_list)
304        self.tblParams.setColumnCount(columns)
305        self.tblParams.setRowCount(rows)
306
307        for i, param in enumerate(param_list):
308            self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param))
309
310        # first - Chi2 and data filename
311        for i_row, (filename, pr) in enumerate(data.items()):
312            out = pr.out
313            cov = pr.cov
[76567bb]314            if out is None:
315                logging.warning("P(r) for {} did not converge.".format(filename))
316                continue
[effdd98]317            self.tblParams.setItem(i_row, 0, QtWidgets.QTableWidgetItem(
[76567bb]318                "{}".format(filename)))
[effdd98]319            self.tblParams.setItem(i_row, 2, QtWidgets.QTableWidgetItem(
320                "{:.3g}".format(pr.chi2[0])))
321            self.tblParams.setItem(i_row, 5, QtWidgets.QTableWidgetItem(
322                "{:.3g}".format(pr.background)))
323            self.tblParams.setItem(i_row, 8, QtWidgets.QTableWidgetItem(
324                "{:.2g}".format(pr.elapsed)))
[76567bb]325            self.tblParams.setItem(i_row, 1, QtWidgets.QTableWidgetItem(
326                "{:.3g}".format(pr.rg(out))))
327            self.tblParams.setItem(i_row, 3, QtWidgets.QTableWidgetItem(
328                "{:.3g}".format(pr.iq0(out))))
329            self.tblParams.setItem(i_row, 4, QtWidgets.QTableWidgetItem(
330                "{:.3g}".format(pr.oscillations(out))))
331            self.tblParams.setItem(i_row, 6, QtWidgets.QTableWidgetItem(
332                "{:.3g}".format(pr.get_positive(out))))
333            self.tblParams.setItem(i_row, 7, QtWidgets.QTableWidgetItem(
334                "{:.3g}".format(pr.get_pos_err(out, cov))))
[effdd98]335
336        self.tblParams.resizeColumnsToContents()
[76567bb]337
338    @classmethod
339    def onHelp(cls):
340        """
341        Open a local url in the default browser
342        """
343        location = GuiUtils.HELP_DIRECTORY_LOCATION
[318b353e]344        url = "/user/sasgui/perspectives/pr/pr_help.html#batch-fit-mode"
[76567bb]345        try:
346            webbrowser.open('file://' + os.path.realpath(location + url))
347        except webbrowser.Error as ex:
348            logging.warning("Cannot display help. %s" % ex)
[98485fe]349
350    def closeEvent(self, event):
351        """Tell the parent window the window closed"""
[ae34d30]352        self.parent.batchResultsWindow = None
[98485fe]353        event.accept()
Note: See TracBrowser for help on using the repository browser.