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

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

Implemented new GUI for data operation panel

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