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

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 f0bb711 was f0bb711, checked in by celinedurniak <celine.durniak@…>, 7 years ago

Implemented comments from review for Data Operation Panel (ESS-GUI-SasView?-245)

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