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

Last change on this file since 6c7ebb88 was 33c0561, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Replace Apply button menu driven functionality with additional button.
Removed Cancel.
Removed the window system context help button from all affected widgets.
SASVIEW-1239

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