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

Last change on this file since 6b43c58 was 7d9c83c, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Minor changes to logging and looping in DataOperation? calculator

  • Property mode set to 100644
File size: 16.7 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
125        self.cbData1.clear()
126        self.cbData1.addItems(['No Data Available'])
127        self.cbData2.clear()
128        self.cbData2.addItems(['No Data Available'])
129        self.close()
130
131
132    def onCompute(self):
133        """ perform calculation """
134        # set operator to be applied
135        operator = self.cbOperator.currentText()
136        # calculate and send data to DataExplorer
137        output = None
138        try:
139            data1 = self.data1
140            data2 = self.data2
141            exec "output = data1 %s data2" % operator
142        except:
143            raise
144
145        self.output = output
146
147        # if outputname was unused, write output result to it
148        # and display plot
149        if self.onCheckOutputName():
150            # add outputname to self.filenames
151            self.list_data_items.append(str(self.txtOutputData.text()))
152            # send result to DataExplorer
153            self.onPrepareOutputData()
154            # plot result
155            self.updatePlot(self.graphOutput, self.layoutOutput, self.output)
156
157    def onPrepareOutputData(self):
158        """ Prepare datasets to be added to DataExplorer and DataManager """
159        new_item = GuiUtils.createModelItemWithPlot(
160            QtCore.QVariant(self.output),
161            name=self.txtOutputData.text())
162
163        new_datalist_item = {str(self.txtOutputData.text()) + str(time.time()):
164                                 self.output}
165        self.communicator. \
166            updateModelFromDataOperationPanelSignal.emit(new_item, new_datalist_item)
167
168    def onSelectOperator(self):
169        """ Change GUI when operator changed """
170        self.lblOperatorApplied.setText(self.cbOperator.currentText())
171        self.newPlot(self.graphOutput, self.layoutOutput)
172
173    def onReset(self):
174        """
175        Reset Panel to its initial state (default values) keeping
176        the names of loaded data
177        """
178        self.txtNumber.setText('1.0')
179        self.txtOutputData.setText('MyNewDataName')
180
181        self.txtNumber.setEnabled(False)
182        self.cmdCompute.setEnabled(False)
183
184        self.cbData1.setCurrentIndex(0)
185        self.cbData2.setCurrentIndex(0)
186        self.cbOperator.setCurrentIndex(0)
187
188        self.output = None
189        self.data1 = None
190        self.data2 = None
191        self.filenames = None
192        self.list_data_items = []
193
194        self.data1OK = False
195        self.data2OK = False
196
197        # Empty graphs
198        self.newPlot(self.graphOutput, self.layoutOutput)
199        self.newPlot(self.graphData1, self.layoutData1)
200        self.newPlot(self.graphData2, self.layoutData2)
201
202    def onSelectData1(self):
203        """ Plot for selection of Data1 """
204        choice_data1 = str(self.cbData1.currentText())
205
206        wrong_choices = ['No Data Available', 'Select Data', '']
207
208        if choice_data1 in wrong_choices:
209            # check validity of choice: input = filename
210            self.newPlot(self.graphData1, self.layoutData1)
211            self.data1 = None
212            self.data1OK = False
213            self.cmdCompute.setEnabled(False) # self.onCheckChosenData())
214            return
215
216        else:
217            self.data1OK = True
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            # Enable Compute button only if Data2 is defined and data compatible
226            self.cmdCompute.setEnabled(self.onCheckChosenData())
227
228    def onSelectData2(self):
229        """ Plot for selection of Data2 """
230        choice_data2 = str(self.cbData2.currentText())
231        wrong_choices = ['No Data Available', 'Select Data', '']
232
233        if choice_data2 in wrong_choices:
234            self.newPlot(self.graphData2, self.layoutData2)
235            self.txtNumber.setEnabled(False)
236            self.data2OK = False
237            self.onCheckChosenData()
238            self.cmdCompute.setEnabled(False)
239            return
240
241        elif choice_data2 == 'Number':
242            self.data2OK = True
243            self.txtNumber.setEnabled(True)
244            self.data2 = float(self.txtNumber.text())
245
246            # Enable Compute button only if Data1 defined and compatible data
247            self.cmdCompute.setEnabled(self.onCheckChosenData())
248            # Display value of coefficient in graphData2
249            self.updatePlot(self.graphData2, self.layoutData2, self.data2)
250            # plot default for output graph
251            self.newPlot(self.graphOutput, self.layoutOutput)
252            self.onCheckChosenData()
253
254        else:
255            self.txtNumber.setEnabled(False)
256            self.data2OK = True
257            key_id2 = self._findId(choice_data2)
258            self.data2 = self._extractData(key_id2)
259            self.cmdCompute.setEnabled(self.onCheckChosenData())
260
261            # plot Data2
262            self.updatePlot(self.graphData2, self.layoutData2, self.data2)
263            # plot default for output graph
264            self.newPlot(self.graphOutput, self.layoutOutput)
265
266    def onInputCoefficient(self):
267        """ Check input of number when a coefficient is required
268        for operation """
269        if self.txtNumber.isModified():
270            input_to_check = str(self.txtNumber.text())
271
272            if input_to_check is None or input_to_check is '':
273                msg = 'DataOperation: Number requires a float number'
274                logging.warning(msg)
275                self.txtNumber.setStyleSheet(QtCore.QString(BG_RED))
276
277            elif float(self.txtNumber.text()) == 0.:
278                # should be check that 0 is not chosen
279                msg = 'DataOperation: Number requires a non zero number'
280                logging.warning(msg)
281                self.txtNumber.setStyleSheet(QtCore.QString(BG_RED))
282
283            else:
284                self.txtNumber.setStyleSheet(QtCore.QString(BG_WHITE))
285                self.data2 = float(self.txtNumber.text())
286                self.updatePlot(self.graphData2, self.layoutData2, self.data2)
287
288    def onCheckChosenData(self):
289        """ check that data1 and data2 are compatible """
290
291        if not all([self.data1OK, self.data2OK]):
292            return False
293        else:
294            if self.cbData2.currentText() == 'Number':
295                self.cbData1.setStyleSheet(QtCore.QString(BG_WHITE))
296                self.cbData2.setStyleSheet(QtCore.QString(BG_WHITE))
297                return True
298
299            elif self.data1.__class__.__name__ != self.data2.__class__.__name__:
300                self.cbData1.setStyleSheet(QtCore.QString(BG_RED))
301                self.cbData2.setStyleSheet(QtCore.QString(BG_RED))
302                print self.data1.__class__.__name__ != self.data2.__class__.__name__
303                logging.warning('Cannot compute data of different dimensions')
304                return False
305
306            elif self.data1.__class__.__name__ == 'Data1D'\
307                    and (len(self.data2.x) != len(self.data1.x) or
308                             not all(i == j for i, j in zip(self.data1.x, self.data2.x))):
309                logging.warning('Cannot compute 1D data of different lengths')
310                self.cbData1.setStyleSheet(QtCore.QString(BG_RED))
311                self.cbData2.setStyleSheet(QtCore.QString(BG_RED))
312                return False
313
314            elif self.data1.__class__.__name__ == 'Data2D' \
315                    and (len(self.data2.qx_data) != len(self.data1.qx_data) \
316                    or len(self.data2.qy_data) != len(self.data1.qy_data)
317                    or not all(i == j for i, j in
318                                     zip(self.data1.qx_data, self.data2.qx_data))
319                    or not all(i == j for i, j in
320                                zip(self.data1.qy_data, self.data2.qy_data))
321                         ):
322                self.cbData1.setStyleSheet(QtCore.QString(BG_RED))
323                self.cbData2.setStyleSheet(QtCore.QString(BG_RED))
324                logging.warning('Cannot compute 2D data of different lengths')
325                return False
326
327            else:
328                self.cbData1.setStyleSheet(QtCore.QString(BG_WHITE))
329                self.cbData2.setStyleSheet(QtCore.QString(BG_WHITE))
330                return True
331
332    def onCheckOutputName(self):
333        """ Check that name of output does not already exist """
334        name_to_check = str(self.txtOutputData.text())
335        self.txtOutputData.setStyleSheet(QtCore.QString(BG_WHITE))
336
337        if name_to_check is None or name_to_check == '':
338            self.txtOutputData.setStyleSheet(QtCore.QString(BG_RED))
339            logging.warning('No output name')
340            return False
341
342        elif name_to_check in self.list_data_items:
343            self.txtOutputData.setStyleSheet(QtCore.QString(BG_RED))
344            logging.warning('The Output data name already exists')
345            return False
346
347        else:
348            self.txtOutputData.setStyleSheet(QtCore.QString(BG_WHITE))
349            return True
350
351    # ########
352    # Modification of inputs
353    # ########
354    def _findId(self, name):
355        """ find id of name in list of filenames """
356        isinstance(name, basestring)
357
358        for key_id in self.filenames.keys():
359            # data with title
360            if self.filenames[key_id].get_data().title:
361                input = self.filenames[key_id].get_data().title
362            # data without title
363            else:
364                input = str(key_id)
365            if name in input:
366                return key_id
367
368    def _extractData(self, key_id):
369        """ Extract data from file with id contained in list of filenames """
370        data_complete = self.filenames[key_id].get_data()
371        dimension = data_complete.__class__.__name__
372
373        if dimension in ('Data1D', 'Data2D'):
374            return copy.deepcopy(data_complete)
375
376        else:
377            logging.warning('Error with data format')
378            return
379
380    # ########
381    # PLOTS
382    # ########
383    def newPlot(self, graph, layout):
384        """ Create template for graphs with default '?' layout"""
385        assert isinstance(graph, QtGui.QGraphicsView)
386        assert isinstance(layout, QtGui.QHBoxLayout)
387
388        # clear layout
389        if layout.count() > 0:
390            item = layout.takeAt(0)
391            layout.removeItem(item)
392
393        layout.setContentsMargins(0, 0, 0, 0)
394        layout.addWidget(self.prepareSubgraphWithData("?"))
395
396        graph.setLayout(layout)
397
398    def updatePlot(self, graph, layout, data):
399        """ plot data in graph after clearing its layout """
400
401        assert isinstance(graph, QtGui.QGraphicsView)
402        assert isinstance(layout, QtGui.QHBoxLayout)
403
404        # clear layout
405        if layout.count() > 0:
406            item = layout.takeAt(0)
407            layout.removeItem(item)
408
409        layout.setContentsMargins(0, 0, 0, 0)
410
411        if isinstance(data, Data2D):
412            # plot 2D data
413            plotter2D = Plotter2DWidget(self, quickplot=True)
414            plotter2D.data = data
415            plotter2D.scale = 'linear'
416
417            plotter2D.ax.tick_params(axis='x', labelsize=8)
418            plotter2D.ax.tick_params(axis='y', labelsize=8)
419
420            # Draw zero axis lines.
421            plotter2D.ax.axhline(linewidth=1)
422            plotter2D.ax.axvline(linewidth=1)
423
424            graph.setLayout(layout)
425            layout.addWidget(plotter2D)
426            # remove x- and ylabels
427            plotter2D.y_label = ''
428            plotter2D.x_label = ''
429            plotter2D.plot(show_colorbar=False)
430            plotter2D.show()
431
432        elif isinstance(data, Data1D):
433            # plot 1D data
434            plotter = PlotterWidget(self, quickplot=True)
435            plotter.data = data
436
437            graph.setLayout(layout)
438            layout.addWidget(plotter)
439
440            plotter.ax.tick_params(axis='x', labelsize=8)
441            plotter.ax.tick_params(axis='y', labelsize=8)
442
443            plotter.plot(hide_error=True, marker='.')
444            # plotter.legend = None
445
446            plotter.show()
447
448        elif float(data) and self.cbData2.currentText() == 'Number':
449            # display value of coefficient (to be applied to Data1)
450            # in graphData2
451            layout.addWidget(self.prepareSubgraphWithData(data))
452
453            graph.setLayout(layout)
454
455    def prepareSubgraphWithData(self, data):
456        """ Create graphics view containing scene with string """
457        scene = QtGui.QGraphicsScene()
458        scene.addText(str(data))
459
460        subgraph = QtGui.QGraphicsView()
461        subgraph.setScene(scene)
462
463        return subgraph
Note: See TracBrowser for help on using the repository browser.