source: sasview/src/sas/qtgui/Calculators/DataOperationUtilityPanel.py @ d6b8a1d

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 d6b8a1d was d6b8a1d, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

More Qt5 related fixes

  • Property mode set to 100644
File size: 16.6 KB
Line 
1import time
2import logging
3import re
4import copy
5
6from PyQt5 import QtCore
7from PyQt5 import QtGui
8from PyQt5 import QtWidgets
9
10from sas.qtgui.Plotting.PlotterData import Data1D
11from sas.qtgui.Plotting.Plotter import PlotterWidget
12from sas.qtgui.Plotting.PlotterData import Data2D
13from sas.qtgui.Plotting.Plotter2D import Plotter2DWidget
14import sas.qtgui.Utilities.GuiUtils as GuiUtils
15
16from .UI.DataOperationUtilityUI import Ui_DataOperationUtility
17
18BG_WHITE = "background-color: rgb(255, 255, 255);"
19BG_RED = "background-color: rgb(244, 170, 164);"
20
21
22class DataOperationUtilityPanel(QtWidgets.QDialog, Ui_DataOperationUtility):
23    def __init__(self, parent=None):
24        super(DataOperationUtilityPanel, self).__init__()
25        self.setupUi(self)
26        self.manager = parent
27        self.communicator = self.manager.communicator()
28
29        # To store input datafiles
30        self.filenames = None
31        self.list_data_items = []
32        self.data1 = None
33        self.data2 = None
34        # To store the result
35        self.output = None
36
37        # To update content of comboboxes with files loaded in DataExplorer
38        self.communicator.sendDataToPanelSignal.connect(self.updateCombobox)
39
40        # change index of comboboxes
41        self.cbData1.currentIndexChanged.connect(self.onSelectData1)
42        self.cbData2.currentIndexChanged.connect(self.onSelectData2)
43        self.cbOperator.currentIndexChanged.connect(self.onSelectOperator)
44
45        # edit Coefficient text edit
46        self.txtNumber.textChanged.connect(self.onInputCoefficient)
47        self.txtOutputData.textChanged.connect(self.onCheckOutputName)
48
49        # push buttons
50        self.cmdClose.clicked.connect(self.onClose)
51        self.cmdHelp.clicked.connect(self.onHelp)
52        self.cmdCompute.clicked.connect(self.onCompute)
53        self.cmdReset.clicked.connect(self.onReset)
54
55        self.cmdCompute.setEnabled(False)
56
57        # validator for coefficient
58        self.txtNumber.setValidator(GuiUtils.DoubleValidator())
59
60        self.layoutOutput = QtWidgets.QHBoxLayout()
61        self.layoutData1 = QtWidgets.QHBoxLayout()
62        self.layoutData2 = QtWidgets.QHBoxLayout()
63
64        # Create default layout for initial graphs (when they are still empty)
65        self.newPlot(self.graphOutput, self.layoutOutput)
66        self.newPlot(self.graphData1, self.layoutData1)
67        self.newPlot(self.graphData2, self.layoutData2)
68
69        # Flag to enable Compute pushbutton
70        self.data2OK = False
71        self.data1OK = False
72
73    def updateCombobox(self, filenames):
74        """ Function to fill comboboxes with names of datafiles loaded in
75         DataExplorer. For Data2, there is the additional option of choosing
76         a number to apply to data1 """
77        self.filenames = filenames
78
79        if list(filenames.keys()):
80            # clear contents of comboboxes
81            self.cbData1.clear()
82            self.cbData1.addItems(['Select Data'])
83            self.cbData2.clear()
84            self.cbData2.addItems(['Select Data', 'Number'])
85
86            list_datafiles = []
87
88            for key_id in list(filenames.keys()):
89                if filenames[key_id].get_data().title:
90                    # filenames with titles
91                    new_title = filenames[key_id].get_data().title
92                    list_datafiles.append(new_title)
93                    self.list_data_items.append(new_title)
94
95                else:
96                    # filenames without titles by removing time.time()
97                    new_title = re.sub('\d{10}\.\d{2}', '', str(key_id))
98                    self.list_data_items.append(new_title)
99                    list_datafiles.append(new_title)
100
101            # update contents of comboboxes
102            self.cbData1.addItems(list_datafiles)
103            self.cbData2.addItems(list_datafiles)
104
105    def onHelp(self):
106        """
107        Bring up the Data Operation Utility Documentation whenever
108        the HELP button is clicked.
109        Calls Documentation Window with the path of the location within the
110        documentation tree (after /doc/ ....".
111        """
112        try:
113            location = GuiUtils.HELP_DIRECTORY_LOCATION + \
114                       "/user/sasgui/perspectives/calculator/data_operator_help.html"
115            self.manager._helpView.load(QtCore.QUrl(location))
116            self.manager._helpView.show()
117
118        except AttributeError:
119            # No manager defined - testing and standalone runs
120            pass
121
122    def onClose(self):
123        """ Close dialog """
124        self.onReset()
125
126        self.cbData1.clear()
127        self.cbData1.addItems(['No Data Available'])
128        self.cbData2.clear()
129        self.cbData2.addItems(['No Data Available'])
130        self.close()
131
132
133    def onCompute(self):
134        """ perform calculation """
135        # set operator to be applied
136        operator = self.cbOperator.currentText()
137        # calculate and send data to DataExplorer
138        output = None
139        try:
140            data1 = self.data1
141            data2 = self.data2
142            output = eval("data1 %s data2" % operator)
143        except Exception as ex:
144            logging.error(ex)
145            return
146
147        self.output = output
148
149        # if outputname was unused, write output result to it
150        # and display plot
151        if self.onCheckOutputName():
152            # add outputname to self.filenames
153            self.list_data_items.append(str(self.txtOutputData.text()))
154            # send result to DataExplorer
155            self.onPrepareOutputData()
156            # plot result
157            self.updatePlot(self.graphOutput, self.layoutOutput, self.output)
158
159    def onPrepareOutputData(self):
160        """ Prepare datasets to be added to DataExplorer and DataManager """
161        new_item = GuiUtils.createModelItemWithPlot(
162            self.output,
163            name=self.txtOutputData.text())
164
165        new_datalist_item = {str(self.txtOutputData.text()) + str(time.time()):
166                                 self.output}
167        self.communicator. \
168            updateModelFromDataOperationPanelSignal.emit(new_item, new_datalist_item)
169
170    def onSelectOperator(self):
171        """ Change GUI when operator changed """
172        self.lblOperatorApplied.setText(self.cbOperator.currentText())
173        self.newPlot(self.graphOutput, self.layoutOutput)
174
175    def onReset(self):
176        """
177        Reset Panel to its initial state (default values) keeping
178        the names of loaded data
179        """
180        self.txtNumber.setText('1.0')
181        self.txtOutputData.setText('MyNewDataName')
182
183        self.txtNumber.setEnabled(False)
184        self.cmdCompute.setEnabled(False)
185
186        self.cbData1.setCurrentIndex(0)
187        self.cbData2.setCurrentIndex(0)
188        self.cbOperator.setCurrentIndex(0)
189
190        self.output = None
191        self.data1 = None
192        self.data2 = None
193        self.filenames = None
194        self.list_data_items = []
195
196        self.data1OK = False
197        self.data2OK = False
198
199        # Empty graphs
200        self.newPlot(self.graphOutput, self.layoutOutput)
201        self.newPlot(self.graphData1, self.layoutData1)
202        self.newPlot(self.graphData2, self.layoutData2)
203
204    def onSelectData1(self):
205        """ Plot for selection of Data1 """
206        choice_data1 = str(self.cbData1.currentText())
207
208        wrong_choices = ['No Data Available', 'Select Data', '']
209
210        if choice_data1 in wrong_choices:
211            # check validity of choice: input = filename
212            self.newPlot(self.graphData1, self.layoutData1)
213            self.data1 = None
214            self.data1OK = False
215            self.cmdCompute.setEnabled(False) # self.onCheckChosenData())
216            return
217
218        else:
219            self.data1OK = True
220            # get Data1
221            key_id1 = self._findId(choice_data1)
222            self.data1 = self._extractData(key_id1)
223            # plot Data1
224            self.updatePlot(self.graphData1, self.layoutData1, self.data1)
225            # plot default for output graph
226            self.newPlot(self.graphOutput, self.layoutOutput)
227            # Enable Compute button only if Data2 is defined and data compatible
228            self.cmdCompute.setEnabled(self.onCheckChosenData())
229
230    def onSelectData2(self):
231        """ Plot for selection of Data2 """
232        choice_data2 = str(self.cbData2.currentText())
233        wrong_choices = ['No Data Available', 'Select Data', '']
234
235        if choice_data2 in wrong_choices:
236            self.newPlot(self.graphData2, self.layoutData2)
237            self.txtNumber.setEnabled(False)
238            self.data2OK = False
239            self.onCheckChosenData()
240            self.cmdCompute.setEnabled(False)
241            return
242
243        elif choice_data2 == 'Number':
244            self.data2OK = True
245            self.txtNumber.setEnabled(True)
246            self.data2 = float(self.txtNumber.text())
247
248            # Enable Compute button only if Data1 defined and compatible data
249            self.cmdCompute.setEnabled(self.onCheckChosenData())
250            # Display value of coefficient in graphData2
251            self.updatePlot(self.graphData2, self.layoutData2, self.data2)
252            # plot default for output graph
253            self.newPlot(self.graphOutput, self.layoutOutput)
254            self.onCheckChosenData()
255
256        else:
257            self.txtNumber.setEnabled(False)
258            self.data2OK = True
259            key_id2 = self._findId(choice_data2)
260            self.data2 = self._extractData(key_id2)
261            self.cmdCompute.setEnabled(self.onCheckChosenData())
262
263            # plot Data2
264            self.updatePlot(self.graphData2, self.layoutData2, self.data2)
265            # plot default for output graph
266            self.newPlot(self.graphOutput, self.layoutOutput)
267
268    def onInputCoefficient(self):
269        """ Check input of number when a coefficient is required
270        for operation """
271        if self.txtNumber.isModified():
272            input_to_check = str(self.txtNumber.text())
273
274            if input_to_check is None or input_to_check is '':
275                msg = 'DataOperation: Number requires a float number'
276                logging.warning(msg)
277                self.txtNumber.setStyleSheet(BG_RED)
278
279            elif float(self.txtNumber.text()) == 0.:
280                # should be check that 0 is not chosen
281                msg = 'DataOperation: Number requires a non zero number'
282                logging.warning(msg)
283                self.txtNumber.setStyleSheet(BG_RED)
284
285            else:
286                self.txtNumber.setStyleSheet(BG_WHITE)
287                self.data2 = float(self.txtNumber.text())
288                self.updatePlot(self.graphData2, self.layoutData2, self.data2)
289
290    def onCheckChosenData(self):
291        """ check that data1 and data2 are compatible """
292
293        if not all([self.data1OK, self.data2OK]):
294            return False
295        else:
296            if self.cbData2.currentText() == 'Number':
297                self.cbData1.setStyleSheet(BG_WHITE)
298                self.cbData2.setStyleSheet(BG_WHITE)
299                return True
300
301            elif self.data1.__class__.__name__ != self.data2.__class__.__name__:
302                self.cbData1.setStyleSheet(BG_RED)
303                self.cbData2.setStyleSheet(BG_RED)
304                print(self.data1.__class__.__name__ != self.data2.__class__.__name__)
305                logging.warning('Cannot compute data of different dimensions')
306                return False
307
308            elif self.data1.__class__.__name__ == 'Data1D'\
309                    and (len(self.data2.x) != len(self.data1.x) or
310                             not all(i == j for i, j in zip(self.data1.x, self.data2.x))):
311                logging.warning('Cannot compute 1D data of different lengths')
312                self.cbData1.setStyleSheet(BG_RED)
313                self.cbData2.setStyleSheet(BG_RED)
314                return False
315
316            elif self.data1.__class__.__name__ == 'Data2D' \
317                    and (len(self.data2.qx_data) != len(self.data1.qx_data) \
318                    or len(self.data2.qy_data) != len(self.data1.qy_data)
319                    or not all(i == j for i, j in
320                                     zip(self.data1.qx_data, self.data2.qx_data))
321                    or not all(i == j for i, j in
322                                zip(self.data1.qy_data, self.data2.qy_data))
323                         ):
324                self.cbData1.setStyleSheet(BG_RED)
325                self.cbData2.setStyleSheet(BG_RED)
326                logging.warning('Cannot compute 2D data of different lengths')
327                return False
328
329            else:
330                self.cbData1.setStyleSheet(BG_WHITE)
331                self.cbData2.setStyleSheet(BG_WHITE)
332                return True
333
334    def onCheckOutputName(self):
335        """ Check that name of output does not already exist """
336        name_to_check = str(self.txtOutputData.text())
337        self.txtOutputData.setStyleSheet(BG_WHITE)
338
339        if name_to_check is None or name_to_check == '':
340            self.txtOutputData.setStyleSheet(BG_RED)
341            logging.warning('No output name')
342            return False
343
344        elif name_to_check in self.list_data_items:
345            self.txtOutputData.setStyleSheet(BG_RED)
346            logging.warning('The Output data name already exists')
347            return False
348
349        else:
350            self.txtOutputData.setStyleSheet(BG_WHITE)
351            return True
352
353    # ########
354    # Modification of inputs
355    # ########
356    def _findId(self, name):
357        """ find id of name in list of filenames """
358        isinstance(name, str)
359
360        for key_id in list(self.filenames.keys()):
361            # data with title
362            if self.filenames[key_id].get_data().title:
363                input = self.filenames[key_id].get_data().title
364            # data without title
365            else:
366                input = str(key_id)
367            if name in input:
368                return key_id
369
370    def _extractData(self, key_id):
371        """ Extract data from file with id contained in list of filenames """
372        data_complete = self.filenames[key_id].get_data()
373        dimension = data_complete.__class__.__name__
374
375        if dimension in ('Data1D', 'Data2D'):
376            return copy.deepcopy(data_complete)
377
378        else:
379            logging.warning('Error with data format')
380            return
381
382    # ########
383    # PLOTS
384    # ########
385    def newPlot(self, graph, layout):
386        """ Create template for graphs with default '?' layout"""
387        assert isinstance(graph, QtWidgets.QGraphicsView)
388        assert isinstance(layout, QtWidgets.QHBoxLayout)
389
390        # clear layout
391        if layout.count() > 0:
392            item = layout.takeAt(0)
393            layout.removeItem(item)
394
395        layout.setContentsMargins(0, 0, 0, 0)
396        layout.addWidget(self.prepareSubgraphWithData("?"))
397
398        graph.setLayout(layout)
399
400    def updatePlot(self, graph, layout, data):
401        """ plot data in graph after clearing its layout """
402
403        assert isinstance(graph, QtWidgets.QGraphicsView)
404        assert isinstance(layout, QtWidgets.QHBoxLayout)
405
406        # clear layout
407        if layout.count() > 0:
408            item = layout.takeAt(0)
409            layout.removeItem(item)
410
411        layout.setContentsMargins(0, 0, 0, 0)
412
413        if isinstance(data, Data2D):
414            # plot 2D data
415            plotter2D = Plotter2DWidget(self, quickplot=True)
416            plotter2D.data = data
417            plotter2D.scale = 'linear'
418
419            plotter2D.ax.tick_params(axis='x', labelsize=8)
420            plotter2D.ax.tick_params(axis='y', labelsize=8)
421
422            # Draw zero axis lines.
423            plotter2D.ax.axhline(linewidth=1)
424            plotter2D.ax.axvline(linewidth=1)
425
426            graph.setLayout(layout)
427            layout.addWidget(plotter2D)
428            # remove x- and ylabels
429            plotter2D.y_label = ''
430            plotter2D.x_label = ''
431            plotter2D.plot(show_colorbar=False)
432            plotter2D.show()
433
434        elif isinstance(data, Data1D):
435            # plot 1D data
436            plotter = PlotterWidget(self, quickplot=True)
437            plotter.data = data
438
439            graph.setLayout(layout)
440            layout.addWidget(plotter)
441
442            plotter.ax.tick_params(axis='x', labelsize=8)
443            plotter.ax.tick_params(axis='y', labelsize=8)
444
445            plotter.plot(hide_error=True, marker='.')
446            # plotter.legend = None
447
448            plotter.show()
449
450        elif float(data) and self.cbData2.currentText() == 'Number':
451            # display value of coefficient (to be applied to Data1)
452            # in graphData2
453            layout.addWidget(self.prepareSubgraphWithData(data))
454
455            graph.setLayout(layout)
456
457    def prepareSubgraphWithData(self, data):
458        """ Create graphics view containing scene with string """
459        scene = QtWidgets.QGraphicsScene()
460        scene.addText(str(data))
461
462        subgraph = QtWidgets.QGraphicsView()
463        subgraph.setScene(scene)
464
465        return subgraph
Note: See TracBrowser for help on using the repository browser.