source: sasview/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py @ f721030

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 f721030 was f721030, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Initial commit of the main window prototype

  • Property mode set to 100755
File size: 20.9 KB
Line 
1# global
2import sys
3import os
4from PyQt4 import QtCore
5from PyQt4 import QtGui
6from PyQt4 import QtWebKit
7
8from twisted.internet import threads
9
10# sas-global
11from sas.sascalc.invariant import invariant
12from sas.sasgui.guiframe.dataFitting import Data1D
13
14# local
15from UI.TabbedInvariantUI import tabbedInvariantUI
16from InvariantDetails import DetailsDialog
17from Plotter import Plotter
18from InvariantUtils import WIDGETS
19
20# The minimum q-value to be used when extrapolating
21Q_MINIMUM = 1e-5
22# The maximum q-value to be used when extrapolating
23Q_MAXIMUM = 10
24# the ratio of maximum q value/(qmax of data) to plot the theory data
25Q_MAXIMUM_PLOT = 3
26
27
28class MyModel(object):
29    def __init__(self):
30        self._model = QtGui.QStandardItemModel(self)
31
32    def addItem(self, item):
33        item = QtGui.QStandardItem(str(item))
34        self._model.appendRow(item)
35
36# class InvariantWindow(InvariantUI):
37class InvariantWindow(tabbedInvariantUI):
38    # The controller which is responsible for managing signal slots connections
39    # for the gui and providing an interface to the data model.
40    def __init__(self, manager=None, parent=None):
41        super(InvariantWindow, self).__init__(parent)
42        # This controller contains the ui and doesn't inherit it directly!
43        # self.form = InvariantUI()
44        self.setWindowTitle("Invariant Perspective")
45        # initial input params
46        self._background = 0.0
47        self._scale = 1.0
48        self._contrast = 1.0
49        self._porod = 1.0
50        self._npoints_low = 10
51        self._npoints_high = 10
52        self._power_low = 4
53
54        self._manager = manager
55        self._reactor = self._manager.reactor()
56
57        self._helpView = QtWebKit.QWebView()
58        self.detailsDialog = DetailsDialog(self)
59        self._plotter = Plotter(self)
60
61        self._low_extrapolate = False
62        self._low_guinier  = True
63        self._low_fit  = True
64        self._high_extrapolate = False
65        self._high_power_value  = False
66
67
68        # Mask file selector
69        ###################################################
70        self._path = "cyl_400_20.txt"
71        from sas.sascalc.dataloader.loader import  Loader
72        loader = Loader()
73        try:
74            self._data = loader.load(self._path)
75        except:
76            raise
77        ###################################################
78
79        self.lineEdit_8.setText(str(Q_MINIMUM))
80        self.lineEdit_9.setText(str(Q_MAXIMUM))
81
82        # Let's choose the Standard Item Model.
83        self.model = QtGui.QStandardItemModel(self)
84
85        # Connect buttons to slots.
86        # Needs to be done early so default values propagate properly.
87        self.setupSlots()
88
89        # Set up the model.
90        self.setupModel()
91
92        # Set up the mapper
93        self.setupMapper()
94
95    def updateFromModel(self):
96        """
97        update the globals based on the data in the model
98        """
99        self._background = float(self.model.item(WIDGETS.W_BACKGROUND).text())
100        self._contrast   = float(self.model.item(WIDGETS.W_CONTRAST).text())
101        self._scale      = float(self.model.item(WIDGETS.W_SCALE).text())
102
103        # High extrapolate
104        self._low_extrapolate = ( str(self.model.item(WIDGETS.W_ENABLE_LOWQ).text()) == 'true')
105        self._low_points = float(self.model.item(WIDGETS.W_NPTS_LOWQ).text())
106        self._low_guinier  = ( str(self.model.item(WIDGETS.W_LOWQ_GUINIER).text()) == 'true')
107        self._low_fit  = ( str(self.model.item(WIDGETS.W_LOWQ_FIT).text()) == 'true')
108        self._low_power_value  = float(self.model.item(WIDGETS.W_LOWQ_POWER_VALUE).text())
109
110        # High extrapolating
111        self._high_extrapolate = ( str(self.model.item(WIDGETS.W_ENABLE_HIGHQ).text()) == 'true')
112        self._high_points  = float(self.model.item(WIDGETS.W_NPTS_HIGHQ).text())
113        self._high_fit  = ( str(self.model.item(WIDGETS.W_HIGHQ_FIT).text()) == 'true')
114        self._high_power_value  = float(self.model.item(WIDGETS.W_HIGHQ_POWER_VALUE).text())
115
116    def calculate(self):
117        """
118        Use twisted to thread the calculations away.
119        """
120        # Find out if extrapolation needs to be used.
121        extrapolation = None
122        if self._low_extrapolate  and not self._high_extrapolate:
123            extrapolation = "low"
124        elif not self._low_extrapolate  and self._high_extrapolate:
125            extrapolation = "high"
126        elif self._low_extrapolate and self._high_extrapolate:
127            extrapolation = "both"
128        try:
129            # modify the Calculate button to indicate background process
130            self.pushButton.setText("Calculating...")
131            self.pushButton.setEnabled(False)
132            self.style = self.pushButton.styleSheet()
133            self.pushButton.setStyleSheet("background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)")
134            # Send the calculations to separate thread.
135            d = threads.deferToThread(self.calculateThread, extrapolation)
136            # Add deferred callback for call return
137            d.addCallback(self.plotResult)
138        except Exception as ex:
139            # Set the button back to available
140            self.pushButton.setEnabled(True)
141            self.pushButton.setText("Calculate")
142            self.pushButton.setStyleSheet(self.style)
143
144    def plotResult(self, model):
145        """
146        """
147        self._plotter.show()
148        self.model = model
149        self.mapper.toFirst()
150
151        # Set the button back to available
152        self.pushButton.setEnabled(True)
153        self.pushButton.setText("Calculate")
154        self.pushButton.setStyleSheet(self.style)
155
156
157    def calculateThread(self, extrapolation):
158        """
159        """
160        self.updateFromModel()
161
162        qstar_low = 0.0
163        qstar_low_err = 0.0
164        qstar_high = 0.0
165        qstar_high_err = 0.0
166        qstar_total = 0.0
167        qstar_total_low_err = 0.0
168
169        # Prepare the invariant object
170        inv = invariant.InvariantCalculator(data=self._data,
171                                            background = self._background,
172                                            scale = self._scale)
173
174        if self._low_extrapolate:
175            function_low = "power_law"
176            if self._low_guinier:
177                function_low = "guinier"
178            if self._low_fit:
179                self._low_power_value = None
180            inv.set_extrapolation(range="low",
181                                  npts=self._low_points,
182                                  function=function_low,
183                                  power=self._low_power_value)
184
185        if self._high_extrapolate:
186            function_low = "power_law"
187            inv.set_extrapolation(range="high",
188                                  npts=self._high_points,
189                                  function=function_low,
190                                  power=self._low_power_value)
191
192        #Compute invariant
193        # TODO: proper exception handling and logic -
194        # display info, update lineedits, don't run extrapolations etc.
195        calculation_failed = False
196        try:
197            qstar_total, qstar_total_error         = inv.get_qstar_with_error()
198        except Exception as ex:
199            calculation_failed = True
200            # Display relevant information
201            item = QtGui.QStandardItem("ERROR")
202            self.model.setItem(WIDGETS.W_INVARIANT, item)
203            item = QtGui.QStandardItem("ERROR")
204            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
205        try:
206            volume_fraction, volume_fraction_error = \
207                inv.get_volume_fraction_with_error(self._contrast)
208        except Exception as ex:
209            calculation_failed = True
210            # Display relevant information
211            item = QtGui.QStandardItem("ERROR")
212            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
213            item = QtGui.QStandardItem("ERROR")
214            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
215        try:
216            surface, surface_error                 = \
217                inv.get_surface_with_error(self._contrast, self._porod)
218        except Exception as ex:
219            calculation_failed = True
220            # Display relevant information
221            item = QtGui.QStandardItem("ERROR")
222            self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
223            item = QtGui.QStandardItem("ERROR")
224            self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
225
226        if(calculation_failed):
227            # TODO: NOTIFY THE GUI MANAGER!!
228            self.mapper.toFirst()
229            return self.model
230
231        self._plotter.clean()
232        self._plotter.x_label("Q(A$^{-1}$)")
233        self._plotter.y_label("Intensity(cm$^{-1}$)")
234
235        if self._low_extrapolate:
236            # for presentation in InvariantDetails
237            qstar_low, qstar_low_err = inv.get_qstar_low()
238            extrapolated_data = inv.get_extra_data_low(self._low_points)
239            power_low = inv.get_extrapolation_power(range='low')
240
241            #inv.data = extrapolated_data
242            #qstar_total, qstar_total_error = inv.get_qstar_with_error()
243
244            # Plot the chart
245            self._plotter.data(extrapolated_data)
246            self._plotter.title("Low-Q extrapolation")
247            self._plotter.plot()
248
249
250        if self._high_extrapolate:
251            # for presentation in InvariantDetails
252            qmax_plot = Q_MAXIMUM_PLOT * max(self._data.x)
253            if qmax_plot > Q_MAXIMUM:
254                qmax_plot = Q_MAXIMUM
255            qstar_high, qstar_high_err = inv.get_qstar_high()
256            power_high = inv.get_extrapolation_power(range='high')
257            high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
258
259            # find how to add this plot to the existing plot for low_extrapolate
260            # Plot the chart
261            self._plotter.data(high_out_data)
262            self._plotter.title("High-Q extrapolation")
263            self._plotter.plot()
264
265
266        item = QtGui.QStandardItem(str(float('%.5g'% volume_fraction)))
267        self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
268        item = QtGui.QStandardItem(str(float('%.5g'% volume_fraction_error)))
269        self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
270        item = QtGui.QStandardItem(str(float('%.5g'% surface)))
271        self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
272        item = QtGui.QStandardItem(str(float('%.5g'% surface_error)))
273        self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
274        item = QtGui.QStandardItem(str(float('%.5g'% qstar_total)))
275        self.model.setItem(WIDGETS.W_INVARIANT, item)
276        item = QtGui.QStandardItem(str(float('%.5g'% qstar_total_error)))
277        self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
278
279        #item = QtGui.QStandardItem(str(float('%.5g'% qstar_total)))
280        #self.model.setItem(WIDGETS.D_TOTAL_QSTAR, item)
281        #item = QtGui.QStandardItem(str(float('%.5g'% qstar_total_err)))
282        #self.model.setItem(WIDGETS.D_TOTAL_QSTAR_ERR, item)
283        item = QtGui.QStandardItem(str(float('%.5g'% qstar_low)))
284        self.model.setItem(WIDGETS.D_LOW_QSTAR, item)
285        item = QtGui.QStandardItem(str(float('%.5g'% qstar_low_err)))
286        self.model.setItem(WIDGETS.D_LOW_QSTAR_ERR, item)
287        item = QtGui.QStandardItem(str(float('%.5g'% qstar_high)))
288        self.model.setItem(WIDGETS.D_HIGH_QSTAR, item)
289        item = QtGui.QStandardItem(str(float('%.5g'% qstar_high_err)))
290        self.model.setItem(WIDGETS.D_HIGH_QSTAR_ERR, item)
291
292        self.mapper.toFirst()
293
294        return self.model
295               
296
297    def status(self):
298        """
299        """
300        self.detailsDialog.setModel(self.model)
301        self.detailsDialog.showDialog()
302
303    def help(self):
304        """
305        """
306        _TreeLocation = "html/user/sasgui/perspectives/invariant/invariant_help.html"
307        self._helpView.load(QtCore.QUrl(_TreeLocation))
308        self._helpView.show()
309
310    def setupSlots(self):
311        self.pushButton.clicked.connect(self.calculate)
312        self.pushButton_2.clicked.connect(self.status)
313        self.pushButton_3.clicked.connect(self.help)
314
315        self.radioButton.toggled.connect(self.lowGuinierAndPowerToggle)
316        self.radioButton_8.toggled.connect(self.hiFitAndFixToggle)
317
318        self.radioButton_3.toggled.connect(self.lowFitAndFixToggle)
319
320        # Bug workaround for QDataWidgetMapper() not reacting to checkbox clicks.
321        # https://bugreports.qt.io/browse/QTBUG-1818
322        self.checkBox.clicked.connect(self.setFocus)
323        self.checkBox_2.clicked.connect(self.setFocus)
324
325        self.model.itemChanged.connect(self.modelChanged)
326
327    def modelChanged(self, item):
328        """
329        """
330        if item.row() == WIDGETS.W_ENABLE_LOWQ:
331            toggle = (str(item.text()) == 'true')
332            self._low_extrapolate = toggle
333            self.lowQToggle(toggle)
334        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
335            toggle = (str(item.text()) == 'true')
336            self._high_extrapolate = toggle
337            self.highQToggle(toggle)
338       
339    def lowGuinierAndPowerToggle(self, toggle):
340        """
341        """
342        self._low_guinier = toggle
343        toggle = not toggle
344        self.lineEdit_11.setEnabled(toggle)
345        self.radioButton_3.setEnabled(toggle)
346        self.radioButton_4.setEnabled(toggle)
347        self.lineEdit_11.setEnabled(toggle and (not self._low_fit))
348
349    def lowFitAndFixToggle(self, toggle):
350        """
351        """
352        self._low_fit = toggle
353        toggle = not toggle
354        self.lineEdit_11.setEnabled(toggle)
355
356    def hiFitAndFixToggle(self, toggle):
357        """
358        """
359        self.lineEdit_13.setEnabled(toggle)
360
361    def highQToggle(self, clicked):
362        """
363        Disable/enable High Q extrapolation
364        """
365        self.radioButton_7.setEnabled(clicked)
366        self.radioButton_8.setEnabled(clicked)
367        self.lineEdit_12.setEnabled(clicked)
368        self.lineEdit_13.setEnabled(clicked)
369
370    def lowQToggle(self, clicked):
371        """
372        Disable/enable Low Q extrapolation
373        """
374        self.radioButton.setEnabled(clicked)
375        self.radioButton_2.setEnabled(clicked)
376        self.lineEdit_11.setEnabled(clicked and not self._low_fit)
377
378        # Enable subelements
379        self.radioButton_3.setEnabled(clicked and not self._low_guinier)
380        self.radioButton_4.setEnabled(clicked and not self._low_guinier)
381        self.lineEdit_10.setEnabled(clicked and not self._low_guinier)
382
383    def setupModel(self):
384
385        # filename
386        item = QtGui.QStandardItem(self._path)
387        self.model.setItem(WIDGETS.W_FILENAME, item)
388
389        # add Q parameters to the model
390        qmin = min(self._data.x)
391        item = QtGui.QStandardItem(str(qmin))
392        self.model.setItem(WIDGETS.W_QMIN, item)
393        item = QtGui.QStandardItem(str(max(self._data.x)))
394        self.model.setItem(WIDGETS.W_QMAX, item)
395
396        # add custom input params
397        item = QtGui.QStandardItem(str(self._background))
398        self.model.setItem(WIDGETS.W_BACKGROUND, item)
399        item = QtGui.QStandardItem(str(self._contrast))
400        self.model.setItem(WIDGETS.W_CONTRAST, item)
401        item = QtGui.QStandardItem(str(self._scale))
402        self.model.setItem(WIDGETS.W_SCALE, item)
403       
404        # Dialog elements
405        itemf = QtGui.QStandardItem("false")
406        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
407        itemf = QtGui.QStandardItem("false")
408        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
409
410        item = QtGui.QStandardItem(str(self._npoints_low))
411        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
412        item = QtGui.QStandardItem(str(self._npoints_high))
413        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
414
415        itemt = QtGui.QStandardItem("true")
416        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
417
418        itemt = QtGui.QStandardItem("true")
419        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
420        item = QtGui.QStandardItem(str(self._power_low))
421        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
422
423        itemt = QtGui.QStandardItem("true")
424        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
425        item = QtGui.QStandardItem(str(self._power_low))
426        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
427
428
429    def setupMapper(self):
430        # Set up the mapper.
431        self.mapper = QtGui.QDataWidgetMapper(self)
432        self.mapper.setOrientation(QtCore.Qt.Vertical)
433        self.mapper.setModel(self.model)
434
435        # Set up the view on the model for testing
436        # self.tableView.setModel(self.model)
437
438        # Filename
439        self.mapper.addMapping(self.lineEdit, WIDGETS.W_FILENAME)
440        # Qmin/Qmax
441        self.mapper.addMapping(self.lineEdit_2, WIDGETS.W_QMIN)
442        self.mapper.addMapping(self.lineEdit_3, WIDGETS.W_QMAX)
443
444        # Background
445        self.mapper.addMapping(self.lineEdit_4, WIDGETS.W_BACKGROUND)
446        # Scale
447        self.mapper.addMapping(self.lineEdit_5, WIDGETS.W_SCALE)
448        # Contrast
449        self.mapper.addMapping(self.lineEdit_6, WIDGETS.W_CONTRAST)
450
451        # Lowq/Highq items
452        self.mapper.addMapping(self.checkBox, WIDGETS.W_ENABLE_LOWQ)
453        self.mapper.addMapping(self.checkBox_2, WIDGETS.W_ENABLE_HIGHQ)
454
455        self.mapper.addMapping(self.lineEdit_10, WIDGETS.W_NPTS_LOWQ)
456        self.mapper.addMapping(self.radioButton, WIDGETS.W_LOWQ_GUINIER)
457
458        self.mapper.addMapping(self.radioButton_3, WIDGETS.W_LOWQ_FIT)
459        self.mapper.addMapping(self.lineEdit_11, WIDGETS.W_LOWQ_POWER_VALUE)
460
461        self.mapper.addMapping(self.radioButton_7, WIDGETS.W_HIGHQ_FIT)
462        self.mapper.addMapping(self.lineEdit_13, WIDGETS.W_HIGHQ_POWER_VALUE)
463   
464        # Output
465        self.mapper.addMapping(self.lineEdit_14, WIDGETS.W_VOLUME_FRACTION)
466        self.mapper.addMapping(self.lineEdit_15, WIDGETS.W_VOLUME_FRACTION_ERR)
467        self.mapper.addMapping(self.lineEdit_16, WIDGETS.W_SPECIFIC_SURFACE)
468        self.mapper.addMapping(self.lineEdit_17, WIDGETS.W_SPECIFIC_SURFACE_ERR)
469        self.mapper.addMapping(self.lineEdit_19, WIDGETS.W_INVARIANT)
470        self.mapper.addMapping(self.lineEdit_18, WIDGETS.W_INVARIANT_ERR)
471
472        self.mapper.toFirst()
473
474    def setData(self, data_list=None):
475        """
476        receive a list of data and compute invariant
477        """
478        msg = ""
479        data = None
480        if data_list is None:
481            data_list = []
482        if len(data_list) >= 1:
483            if len(data_list) == 1:
484                data = data_list[0]
485            else:
486                data_1d_list = []
487                data_2d_list = []
488                error_msg = ""
489                # separate data into data1d and data2d list
490                for data in data_list:
491                    if data is not None:
492                        if issubclass(data.__class__, Data1D):
493                            data_1d_list.append(data)
494                        else:
495                            error_msg += " %s  type %s \n" % (str(data.name),
496                                                              str(data.__class__.__name__))
497                            data_2d_list.append(data)
498                if len(data_2d_list) > 0:
499                    msg = "Invariant does not support the following data types:\n"
500                    msg += error_msg
501                if len(data_1d_list) == 0:
502                    # remake this as a qt event
503                    #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
504                    return
505                msg += "Invariant panel does not allow multiple data!\n"
506                msg += "Please select one.\n"
507                #if len(data_list) > 1:
508                    #from invariant_widgets import DataDialog
509                    #dlg = DataDialog(data_list=data_1d_list, text=msg)
510                    #if dlg.ShowModal() == wx.ID_OK:
511                    #    data = dlg.get_data()
512                    #else:
513                    #    data = None
514                    #dlg.Destroy()
515
516            if data is None:
517                msg += "invariant receives no data. \n"
518                #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
519                return
520            if not issubclass(data.__class__, Data1D):
521                msg += "invariant cannot be computed for data of "
522                msg += "type %s\n" % (data.__class__.__name__)
523                #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
524                return
525            else:
526                #wx.PostEvent(self.parent, NewPlotEvent(plot=data, title=data.title))
527                try:
528                    self._data = data
529                    self._path = "unique path"
530                    self.calculate()
531                except:
532                    msg = "Invariant Set_data: " + str(sys.exc_value)
533                    #wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
534        else:
535            msg = "invariant cannot be computed for data of "
536            msg += "type %s" % (data.__class__.__name__)
537            #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
538
539
540
541if __name__ == "__main__":
542    app = QtGui.QApplication([])
543    import qt4reactor
544    qt4reactor.install()
545    # DO NOT move the following import to the top!
546    # (unless you know what you're doing)
547    from twisted.internet import reactor
548    dlg = InvariantWindow(reactor)
549    dlg.show()
550    reactor.run()
Note: See TracBrowser for help on using the repository browser.