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

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 f1f3e6a was f1f3e6a, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Initial commit of Celine's Invariant Perspective work SASVIEW-52

  • Property mode set to 100755
File size: 30.8 KB
Line 
1# global
2import sys
3import os
4import logging
5import copy
6import webbrowser
7
8from PyQt5 import QtCore
9from PyQt5 import QtGui, QtWidgets
10from PyQt5 import QtWebKit
11
12from twisted.internet import threads
13from twisted.internet import reactor
14
15# sas-global
16from sas.sascalc.invariant import invariant
17from sas.qtgui.Plotting.PlotterData import Data1D
18import sas.qtgui.Utilities.GuiUtils as GuiUtils
19
20# import sas.qtgui.Plotting.PlotHelper as PlotHelper
21
22# local
23from .UI.TabbedInvariantUI import Ui_tabbedInvariantUI
24from .InvariantDetails import DetailsDialog
25from .InvariantUtils import WIDGETS
26
27# The minimum q-value to be used when extrapolating
28Q_MINIMUM = 1e-5
29# The maximum q-value to be used when extrapolating
30Q_MAXIMUM = 10
31# the ratio of maximum q value/(qmax of data) to plot the theory data
32Q_MAXIMUM_PLOT = 3
33# Default number of points of interpolation: high and low range
34NPOINTS_Q_INTERP = 10
35# Default power law for interpolation
36DEFAULT_POWER_LOW = 4
37
38# Background of line edits if settings OK or wrong
39BG_WHITE = "background-color: rgb(255, 255, 255);"
40BG_RED = "background-color: rgb(244, 170, 164);"
41
42class InvariantWindow(QtWidgets.QDialog, Ui_tabbedInvariantUI):
43    # The controller which is responsible for managing signal slots connections
44    # for the gui and providing an interface to the data model.
45    name = "Invariant"  # For displaying in the combo box in DataExplorer
46
47    def __init__(self, parent=None):
48        super(InvariantWindow, self).__init__()
49        self.setupUi(self)
50
51        self.setWindowTitle("Invariant Perspective")
52
53        # initial input params
54        self._background = 0.0
55        self._scale = 1.0
56        self._contrast = 1.0
57        self._porod = None
58
59        self.parent = parent
60
61        self._manager = parent
62        self._reactor = reactor
63        self._model_item = QtGui.QStandardItem()
64
65        #self._helpView = QtWebKit.QWebView()
66        self.detailsDialog = DetailsDialog(self)
67        self.detailsDialog.cmdOK.clicked.connect(self.enabling)
68
69        self._low_extrapolate = False
70        self._low_guinier = True
71        self._low_fit = False
72        self._low_power_value = False
73        self._low_points = NPOINTS_Q_INTERP
74        self._low_power_value = DEFAULT_POWER_LOW
75
76        self._high_extrapolate = False
77        self._high_power_value = False
78        self._high_fit = False
79        self._high_points = NPOINTS_Q_INTERP
80        self._high_power_value = DEFAULT_POWER_LOW
81
82        # no reason to have this widget resizable
83        self.setFixedSize(self.minimumSizeHint())
84
85        self.communicate = self._manager.communicator()
86
87        self._data = None
88        self._path = ""
89
90        self._allow_close = False
91
92        # Modify font in order to display Angstrom symbol correctly
93        new_font = 'font-family: -apple-system, "Helvetica Neue", "Ubuntu";'
94        self.lblTotalQUnits.setStyleSheet(new_font)
95        self.lblSpecificSurfaceUnits.setStyleSheet(new_font)
96        self.lblInvariantTotalQUnits.setStyleSheet(new_font)
97        self.lblContrastUnits.setStyleSheet(new_font)
98        self.lblPorodCstUnits.setStyleSheet(new_font)
99        self.lblExtrapolQUnits.setStyleSheet(new_font)
100
101        # To remove blue square around line edits
102        self.txtBackgd.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
103        self.txtContrast.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
104        self.txtScale.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
105        self.txtPorodCst.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
106        self.txtNptsHighQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
107        self.txtNptsLowQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
108        self.txtPowerLowQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
109        self.txtPowerHighQ.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
110
111        self.txtExtrapolQMin.setText(str(Q_MINIMUM))
112        self.txtExtrapolQMax.setText(str(Q_MAXIMUM))
113
114        # Let's choose the Standard Item Model.
115        self.model = QtGui.QStandardItemModel(self)
116
117        # Connect buttons to slots.
118        # Needs to be done early so default values propagate properly.
119        self.setupSlots()
120
121        # Set up the model.
122        self.setupModel()
123
124        # Set up the mapper
125        self.setupMapper()
126
127        # validator: double
128        self.txtBackgd.setValidator(QtGui.QDoubleValidator())
129        self.txtContrast.setValidator(QtGui.QDoubleValidator())
130        self.txtScale.setValidator(QtGui.QDoubleValidator())
131        self.txtPorodCst.setValidator(QtGui.QDoubleValidator())
132
133        # validator: integer number
134        valid_regex_int = QtCore.QRegExp('^[+]?(\d+[.][0]*)$')
135        self.txtNptsLowQ.setValidator(QtGui.QRegExpValidator(valid_regex_int,
136                                                             self.txtNptsLowQ))
137        self.txtNptsHighQ.setValidator(QtGui.QRegExpValidator(valid_regex_int,
138                                                             self.txtNptsHighQ))
139        self.txtPowerLowQ.setValidator(QtGui.QRegExpValidator(valid_regex_int,
140                                                             self.txtPowerLowQ))
141        self.txtPowerHighQ.setValidator(QtGui.QRegExpValidator(valid_regex_int,
142                                                             self.txtPowerHighQ))
143
144    def enabling(self):
145        """ """
146        self.cmdStatus.setEnabled(True)
147
148    def setClosable(self, value=True):
149        """ Allow outsiders close this widget """
150        assert isinstance(value, bool)
151
152        self._allow_close = value
153
154    def closeEvent(self, event):
155        """
156        Overwrite QDialog close method to allow for custom widget close
157        """
158        if self._allow_close:
159            # reset the closability flag
160            self.setClosable(value=False)
161            event.accept()
162        else:
163            event.ignore()
164            # Maybe we should just minimize
165            self.setWindowState(QtCore.Qt.WindowMinimized)
166
167    def updateFromModel(self):
168        """ Update the globals based on the data in the model """
169        self._background = float(self.model.item(WIDGETS.W_BACKGROUND).text())
170        self._contrast = float(self.model.item(WIDGETS.W_CONTRAST).text())
171        self._scale = float(self.model.item(WIDGETS.W_SCALE).text())
172        if self.model.item(WIDGETS.W_POROD_CST).text() != 'None' \
173                and self.model.item(WIDGETS.W_POROD_CST).text() != '':
174            self._porod = float(self.model.item(WIDGETS.W_POROD_CST).text())
175
176        # Low extrapolating
177        self._low_extrapolate = str(self.model.item(WIDGETS.W_ENABLE_LOWQ).text()) == 'true'
178        self._low_points = float(self.model.item(WIDGETS.W_NPTS_LOWQ).text())
179        self._low_guinier = str(self.model.item(WIDGETS.W_LOWQ_GUINIER).text()) == 'true'
180        self._low_fit = str(self.model.item(WIDGETS.W_LOWQ_FIT).text()) == 'true'
181        self._low_power_value = float(self.model.item(WIDGETS.W_LOWQ_POWER_VALUE).text())
182
183        # High extrapolating
184        self._high_extrapolate = str(self.model.item(WIDGETS.W_ENABLE_HIGHQ).text()) == 'true'
185        self._high_points = float(self.model.item(WIDGETS.W_NPTS_HIGHQ).text())
186        self._high_fit = str(self.model.item(WIDGETS.W_HIGHQ_FIT).text()) == 'true'
187        self._high_power_value = float(self.model.item(WIDGETS.W_HIGHQ_POWER_VALUE).text())
188
189    def calculateInvariant(self):
190        """ Use twisted to thread the calculations away """
191        # Find out if extrapolation needs to be used.
192        extrapolation = None
193        if self._low_extrapolate and not self._high_extrapolate:
194            extrapolation = "low"
195        elif not self._low_extrapolate and self._high_extrapolate:
196            extrapolation = "high"
197        elif self._low_extrapolate and self._high_extrapolate:
198            extrapolation = "both"
199
200        # modify the Calculate button to indicate background process
201        self.cmdCalculate.setText("Calculating...")
202        self.cmdCalculate.setEnabled(False)
203
204        # Send the calculations to separate thread.
205        d = threads.deferToThread(self.calculateThread, extrapolation)
206
207        # Add deferred callback for call return
208        d.addCallback(self.deferredPlot)
209        d.addErrback(self.calculationFailed)
210
211    def calculationFailed(self, reason):
212        print("calculation failed: ", reason)
213        pass
214
215    def deferredPlot(self, model):
216        """
217        Run the GUI/model update in the main thread
218        """
219        reactor.callFromThread(lambda: self.plotResult(model))
220
221    def plotResult(self, model):
222        """ Plot result of calculation """
223        # Set the button back to available
224        self.cmdCalculate.setEnabled(True)
225        self.cmdCalculate.setText("Calculate")
226        self.cmdStatus.setEnabled(True)
227
228        self.model = model
229        self.mapper.toFirst()
230        self._data = GuiUtils.dataFromItem(self._model_item)
231
232        # Send the modified model item to DE for keeping in the model
233        # Currently -unused
234        #self.communicate.updateModelFromPerspectiveSignal.emit(self._model_item)
235
236        plot_data = GuiUtils.plotsFromCheckedItems(self._manager.filesWidget.model)
237
238        self._manager.filesWidget.plotData(plot_data)
239
240        # Update the details dialog in case it is open
241        self.updateDetailsWidget(model)
242
243    def updateDetailsWidget(self, model):
244        """
245        On demand update of the details widget
246        """
247        if self.detailsDialog.isVisible():
248            self.onStatus()
249
250    def calculateThread(self, extrapolation):
251        """
252        Perform Invariant calculations.
253        TODO: Create a dictionary of results to be sent to DE on completion.
254        """
255        self.updateFromModel()
256        msg = ''
257
258        qstar_low = 0.0
259        qstar_low_err = 0.0
260        qstar_high = 0.0
261        qstar_high_err = 0.0
262        qstar_total = 0.0
263        qstar_total_error = 0.0
264
265        temp_data = copy.deepcopy(self._data)
266
267        # Prepare the invariant object
268        inv = invariant.InvariantCalculator(data=temp_data,
269                                            background=self._background,
270                                            scale=self._scale)
271        if self._low_extrapolate:
272
273            function_low = "power_law"
274            if self._low_guinier:
275                function_low = "guinier"
276            if self._low_fit:
277                self._low_power_value = None
278
279            inv.set_extrapolation(range="low",
280                                  npts=int(self._low_points),
281                                  function=function_low,
282                                  power=self._low_power_value)
283
284        if self._high_extrapolate:
285            function_low = "power_law"
286            inv.set_extrapolation(range="high",
287                                  npts=int(self._high_points),
288                                  function=function_low,
289                                  power=self._high_power_value)
290        # Compute invariant
291        calculation_failed = False
292
293        try:
294            qstar_total, qstar_total_error = inv.get_qstar_with_error()
295        except Exception as ex:
296            msg += str(ex)
297            calculation_failed = True
298            # Display relevant information
299            item = QtGui.QStandardItem("ERROR")
300            self.model.setItem(WIDGETS.W_INVARIANT, item)
301            item = QtGui.QStandardItem("ERROR")
302            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
303
304        try:
305            volume_fraction, volume_fraction_error = \
306                inv.get_volume_fraction_with_error(self._contrast)
307
308        except Exception as ex:
309            calculation_failed = True
310            msg += str(ex)
311            # Display relevant information
312            item = QtGui.QStandardItem("ERROR")
313            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
314            item = QtGui.QStandardItem("ERROR")
315            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
316
317        if self._porod:
318            try:
319                surface, surface_error = \
320                    inv.get_surface_with_error(self._contrast, self._porod)
321            except Exception as ex:
322                calculation_failed = True
323                msg += str(ex)
324                # Display relevant information
325                item = QtGui.QStandardItem("ERROR")
326                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
327                item = QtGui.QStandardItem("ERROR")
328                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
329        else:
330            surface = None
331
332        if (calculation_failed):
333            self.mapper.toFirst()
334            logging.warning('Calculation failed: {}'.format(msg))
335            return self.model
336        else:
337
338            if self._low_extrapolate:
339                # for presentation in InvariantDetails
340                qstar_low, qstar_low_err = inv.get_qstar_low()
341                extrapolated_data = inv.get_extra_data_low(self._low_points)
342                power_low = inv.get_extrapolation_power(range='low')
343
344                # Plot the chart
345                title = "Low-Q extrapolation"
346
347                # Convert the data into plottable
348                extrapolated_data = self._manager.createGuiData(extrapolated_data)
349
350                extrapolated_data.name = title
351                extrapolated_data.title = title
352
353                # copy labels and units of axes for plotting
354                extrapolated_data._xaxis = temp_data._xaxis
355                extrapolated_data._xunit = temp_data._xunit
356                extrapolated_data._yaxis = temp_data._yaxis
357                extrapolated_data._yunit = temp_data._yunit
358
359                # Add the plot to the model item
360                # This needs to run in the main thread
361                reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
362                                       self._model_item,
363                                       extrapolated_data,
364                                       title)
365
366            if self._high_extrapolate:
367                # for presentation in InvariantDetails
368                qmax_plot = Q_MAXIMUM_PLOT * max(temp_data.x) # self._data.x)
369
370                if qmax_plot > Q_MAXIMUM:
371                    qmax_plot = Q_MAXIMUM
372                qstar_high, qstar_high_err = inv.get_qstar_high()
373                power_high = inv.get_extrapolation_power(range='high')
374                high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
375
376                # Plot the chart
377                title = "High-Q extrapolation"
378
379                # Convert the data into plottable
380                high_out_data = self._manager.createGuiData(high_out_data)
381                high_out_data.name = title
382                high_out_data.title = title
383
384                # copy labels and units of axes for plotting
385                high_out_data._xaxis = temp_data._xaxis
386                high_out_data._xunit = temp_data._xunit
387                high_out_data._yaxis = temp_data._yaxis
388                high_out_data._yunit = temp_data._yunit
389
390                # Add the plot to the model item
391                # This needs to run in the main thread
392                reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
393                                       self._model_item, high_out_data, title)
394
395            item = QtGui.QStandardItem(str(float('%.3g'% volume_fraction)))
396            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
397            item = QtGui.QStandardItem(str(float('%.3g'% volume_fraction_error)))
398            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
399            if surface:
400                item = QtGui.QStandardItem(str(float('%.3g'% surface)))
401                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
402                item = QtGui.QStandardItem(str(float('%.3g'% surface_error)))
403                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
404            item = QtGui.QStandardItem(str(float('%.3g'% qstar_total)))
405            self.model.setItem(WIDGETS.W_INVARIANT, item)
406            item = QtGui.QStandardItem(str(float('%.3g'% qstar_total_error)))
407            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
408
409            item = QtGui.QStandardItem(str(float('%.3g'% qstar_low)))
410            self.model.setItem(WIDGETS.D_LOW_QSTAR, item)
411            item = QtGui.QStandardItem(str(float('%.3g'% qstar_low_err)))
412            self.model.setItem(WIDGETS.D_LOW_QSTAR_ERR, item)
413            item = QtGui.QStandardItem(str(float('%.3g'% qstar_high)))
414            self.model.setItem(WIDGETS.D_HIGH_QSTAR, item)
415            item = QtGui.QStandardItem(str(float('%.3g'% qstar_high_err)))
416            self.model.setItem(WIDGETS.D_HIGH_QSTAR_ERR, item)
417
418            self.mapper.toFirst()
419
420            return self.model
421
422    def title(self):
423        """ Perspective name """
424        return "Invariant panel"
425
426    def onStatus(self):
427        """
428        Display Invariant Details panel when clicking on Status button
429        """
430        self.detailsDialog.setModel(self.model)
431        self.detailsDialog.showDialog()
432        self.cmdStatus.setEnabled(False)
433
434    def onHelp(self):
435        """ Display help when clicking on Help button """
436        treeLocation = GuiUtils.HELP_DIRECTORY_LOCATION + \
437            "/user/sasgui/perspectives/invariant/invariant_help.html"
438        webbrowser.open('file://' + treeLocation)
439
440    def setupSlots(self):
441        """ """
442        self.cmdCalculate.clicked.connect(self.calculateInvariant)
443        self.cmdStatus.clicked.connect(self.onStatus)
444        self.cmdHelp.clicked.connect(self.onHelp)
445
446        self.chkLowQ.stateChanged.connect(self.stateChanged)
447        self.chkLowQ.stateChanged.connect(self.checkQExtrapolatedData)
448
449        self.chkHighQ.stateChanged.connect(self.stateChanged)
450        self.chkHighQ.stateChanged.connect(self.checkQExtrapolatedData)
451
452        # slots for the Guinier and PowerLaw radio buttons at low Q
453        # since they are not auto-exclusive
454        self.rbGuinier.toggled.connect(self.lowGuinierAndPowerToggle)
455
456        self.rbPowerLawLowQ.toggled.connect(self.lowGuinierAndPowerToggle)
457
458        self.rbFitHighQ.toggled.connect(self.hiFitAndFixToggle)
459
460        self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
461
462        self.model.itemChanged.connect(self.modelChanged)
463
464        # update model from gui editing by users
465        self.txtBackgd.textChanged.connect(self.updateFromGui)
466
467        self.txtScale.textChanged.connect(self.updateFromGui)
468
469        self.txtContrast.textChanged.connect(self.updateFromGui)
470
471        self.txtPorodCst.textChanged.connect(self.updateFromGui)
472
473        self.txtPowerLowQ.textChanged.connect(self.updateFromGui)
474
475        self.txtPowerHighQ.textChanged.connect(self.updateFromGui)
476
477        self.txtNptsLowQ.textChanged.connect(self.updateFromGui)
478
479        self.txtNptsHighQ.textChanged.connect(self.updateFromGui)
480
481        # check values of n_points compared to distribution length
482        if self.txtNptsLowQ.isEnabled():
483            self.txtNptsLowQ.textChanged.connect(self.checkLength)
484
485        if self.txtNptsHighQ.isEnabled():
486            self.txtNptsHighQ.textChanged.connect(self.checkLength)
487
488    def stateChanged(self):
489        """
490        Catch modifications from low- and high-Q extrapolation check boxes
491        """
492        sender = self.sender()
493
494        itemf = QtGui.QStandardItem(str(sender.isChecked()).lower())
495        if sender.text() == 'Enable Low-Q extrapolation':
496            self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
497
498        if sender.text() == 'Enable High-Q extrapolation':
499            self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
500
501    def checkLength(self):
502        """
503        Validators of number of points for extrapolation.
504        Error if it is larger than the distribution length
505        """
506        if self._data:
507            if len(self._data.x) < int(self.sender().text()):
508                self.sender().setStyleSheet(QtCore.QString(BG_RED))
509                logging.warning('The number of points must be smaller than {}'.format(len(self._data.x)))
510                self.cmdCalculate.setEnabled(False)
511            else:
512                self.sender().setStyleSheet(QtCore.QString(BG_WHITE))
513                self.cmdCalculate.setEnabled(True)
514        else:
515            # logging.info('no data is loaded')
516            self.cmdCalculate.setEnabled(False)
517
518    def modelChanged(self, item):
519        """ Update when model changed """
520        if item.row() == WIDGETS.W_ENABLE_LOWQ:
521            toggle = (str(item.text()) == 'true')
522            self._low_extrapolate = toggle
523            self.lowQToggle(toggle)
524        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
525            toggle = (str(item.text()) == 'true')
526            self._high_extrapolate = toggle
527            self.highQToggle(toggle)
528
529    def checkQExtrapolatedData(self):
530        """
531        Match status of low or high-Q extrapolated data checkbox in
532        DataExplorer with low or high-Q extrapolation checkbox in invariant
533        panel
534        """
535        # name to search in DataExplorer
536        if 'Low' in str(self.sender().text()):
537            name = "Low-Q extrapolation"
538        if 'High' in str(self.sender().text()):
539            name = "High-Q extrapolation"
540
541        GuiUtils.updateModelItemStatus(self._manager.filesWidget.model,
542                                       self._path, name,
543                                       self.sender().checkState())
544
545    def updateFromGui(self):
546        """ Update model when new user inputs """
547        possible_senders = ['txtBackgd', 'txtContrast', 'txtPorodCst',
548                            'txtScale', 'txtPowerLowQ', 'txtPowerHighQ',
549                            'txtNptsLowQ', 'txtNptsHighQ']
550
551        related_widgets = [WIDGETS.W_BACKGROUND, WIDGETS.W_CONTRAST,
552                           WIDGETS.W_POROD_CST, WIDGETS.W_SCALE,
553                           WIDGETS.W_LOWQ_POWER_VALUE, WIDGETS.W_HIGHQ_POWER_VALUE,
554                           WIDGETS.W_NPTS_LOWQ, WIDGETS.W_NPTS_HIGHQ]
555
556        related_internal_values = [self._background, self._contrast,
557                                   self._porod, self._scale,
558                                   self._low_power_value,
559                                   self._high_power_value,
560                                   self._low_points, self._high_points]
561
562        item = QtGui.QStandardItem(self.sender().text())
563
564        index_elt = possible_senders.index(self.sender().objectName())
565
566        self.model.setItem(related_widgets[index_elt], item)
567
568        related_internal_values[index_elt] = float(self.sender().text())
569
570        # print possible_senders[index_elt], related_internal_values[index_elt]
571
572        self.mapper.toFirst()
573
574    def lowGuinierAndPowerToggle(self, toggle):
575        """
576        Guinier and Power radio buttons cannot be selected at the same time
577        If Power is selected, Fit and Fix radio buttons are visible and
578        Power line edit can be edited if Fix is selected
579        """
580        if self.sender().text() == 'Guinier':
581            self._low_guinier = toggle
582
583            toggle = not toggle
584            self.rbPowerLawLowQ.setChecked(toggle)
585
586            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
587            self.rbFitLowQ.setVisible(toggle)
588            self.rbFixLowQ.setVisible(toggle)
589
590            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
591
592        else:
593            self._low_guinier = not toggle
594
595            self.rbGuinier.setChecked(not toggle)
596
597            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
598            self.rbFitLowQ.setVisible(toggle)
599            self.rbFixLowQ.setVisible(toggle)
600
601            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
602
603    def lowFitAndFixToggle(self, toggle):
604        """ Fit and Fix radiobuttons cannot be selected at the same time """
605        self._low_fit = toggle
606
607        toggle = not toggle
608        self.txtPowerLowQ.setEnabled(toggle)
609
610    def hiFitAndFixToggle(self, toggle):
611        """
612        Enable editing of power exponent if Fix for high Q is checked
613        Disable otherwise
614        """
615        self.txtPowerHighQ.setEnabled(not toggle)
616
617    def highQToggle(self, clicked):
618        """ Disable/enable High Q extrapolation """
619        self.rbFitHighQ.setEnabled(clicked)
620        self.rbFixHighQ.setEnabled(clicked)
621        self.txtNptsHighQ.setEnabled(clicked)
622        self.txtPowerHighQ.setEnabled(clicked)
623
624    def lowQToggle(self, clicked):
625        """ Disable / enable Low Q extrapolation """
626        self.rbGuinier.setEnabled(clicked)
627        self.rbPowerLawLowQ.setEnabled(clicked)
628        self.txtNptsLowQ.setEnabled(clicked)
629        # Enable subelements
630        self.rbFitLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
631        self.rbFixLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
632        self.rbFitLowQ.setEnabled(clicked)  # and not self._low_guinier)
633        self.rbFixLowQ.setEnabled(clicked)  # and not self._low_guinier)
634
635        self.txtPowerLowQ.setEnabled(clicked
636                                    and not self._low_guinier
637                                    and not self._low_fit)
638
639    def setupModel(self):
640        """ """
641        # filename
642        item = QtGui.QStandardItem(self._path)
643        self.model.setItem(WIDGETS.W_FILENAME, item)
644
645        # add Q parameters to the model
646        qmin = 0.0
647        item = QtGui.QStandardItem(str(qmin))
648        self.model.setItem(WIDGETS.W_QMIN, item)
649        qmax = 0.0
650        item = QtGui.QStandardItem(str(qmax))
651        self.model.setItem(WIDGETS.W_QMAX, item)
652
653        # add custom input params
654        item = QtGui.QStandardItem(str(self._background))
655        self.model.setItem(WIDGETS.W_BACKGROUND, item)
656        item = QtGui.QStandardItem(str(self._contrast))
657        self.model.setItem(WIDGETS.W_CONTRAST, item)
658        item = QtGui.QStandardItem(str(self._scale))
659        self.model.setItem(WIDGETS.W_SCALE, item)
660        # leave line edit empty if Porod constant not defined
661        if self._porod != None:
662            item = QtGui.QStandardItem(str(self._porod))
663        else:
664            item = QtGui.QStandardItem(str(''))
665        self.model.setItem(WIDGETS.W_POROD_CST, item)
666
667        # Dialog elements
668        itemf = QtGui.QStandardItem("false")
669        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
670        itemf = QtGui.QStandardItem("false")
671        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
672
673        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
674        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
675        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
676        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
677
678        itemt = QtGui.QStandardItem("true")
679        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
680
681        itemt = QtGui.QStandardItem("true")
682        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
683        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
684        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
685
686        itemt = QtGui.QStandardItem("true")
687        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
688        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
689        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
690
691    def setupMapper(self):
692        # Set up the mapper.
693        self.mapper = QtWidgets.QDataWidgetMapper(self)
694        self.mapper.setOrientation(QtCore.Qt.Vertical)
695        self.mapper.setModel(self.model)
696
697        # Filename
698        self.mapper.addMapping(self.txtName, WIDGETS.W_FILENAME)
699
700        # Qmin/Qmax
701        self.mapper.addMapping(self.txtTotalQMin, WIDGETS.W_QMIN)
702        self.mapper.addMapping(self.txtTotalQMax, WIDGETS.W_QMAX)
703
704        # Background
705        self.mapper.addMapping(self.txtBackgd, WIDGETS.W_BACKGROUND)
706
707        # Scale
708        self.mapper.addMapping(self.txtScale, WIDGETS.W_SCALE)
709
710        # Contrast
711        self.mapper.addMapping(self.txtContrast, WIDGETS.W_CONTRAST)
712
713        # Porod constant
714        self.mapper.addMapping(self.txtPorodCst, WIDGETS.W_POROD_CST)
715
716        # Lowq/Highq items
717        self.mapper.addMapping(self.chkLowQ, WIDGETS.W_ENABLE_LOWQ)
718        self.mapper.addMapping(self.chkHighQ, WIDGETS.W_ENABLE_HIGHQ)
719
720        self.mapper.addMapping(self.txtNptsLowQ, WIDGETS.W_NPTS_LOWQ)
721        self.mapper.addMapping(self.rbGuinier, WIDGETS.W_LOWQ_GUINIER)
722        self.mapper.addMapping(self.rbFitLowQ, WIDGETS.W_LOWQ_FIT)
723        self.mapper.addMapping(self.txtPowerLowQ, WIDGETS.W_LOWQ_POWER_VALUE)
724
725        self.mapper.addMapping(self.txtNptsHighQ, WIDGETS.W_NPTS_HIGHQ)
726        self.mapper.addMapping(self.rbFitHighQ, WIDGETS.W_HIGHQ_FIT)
727        self.mapper.addMapping(self.txtPowerHighQ, WIDGETS.W_HIGHQ_POWER_VALUE)
728   
729        # Output
730        self.mapper.addMapping(self.txtVolFract, WIDGETS.W_VOLUME_FRACTION)
731        self.mapper.addMapping(self.txtVolFractErr, WIDGETS.W_VOLUME_FRACTION_ERR)
732        self.mapper.addMapping(self.txtSpecSurf, WIDGETS.W_SPECIFIC_SURFACE)
733        self.mapper.addMapping(self.txtSpecSurfErr, WIDGETS.W_SPECIFIC_SURFACE_ERR)
734        self.mapper.addMapping(self.txtInvariantTot, WIDGETS.W_INVARIANT)
735        self.mapper.addMapping(self.txtInvariantTotErr, WIDGETS.W_INVARIANT_ERR)
736
737        self.mapper.toFirst()
738
739    def setData(self, data_item=None, is_batch=False):
740        """
741        Obtain a QStandardItem object and dissect it to get Data1D/2D
742        Pass it over to the calculator
743        """
744        assert data_item is not None
745
746        if self.txtName.text() == data_item[0].text():
747            logging.info('This file is already loaded in Invariant panel.')
748            return
749
750        if not isinstance(data_item, list):
751            msg = "Incorrect type passed to the Invariant Perspective"
752            raise AttributeError(msg)
753
754        if not isinstance(data_item[0], QtGui.QStandardItem):
755            msg = "Incorrect type passed to the Invariant Perspective"
756            raise AttributeError(msg)
757
758        # only 1 file can be loaded
759        self._model_item = data_item[0]
760
761        # Extract data on 1st child - this is the Data1D/2D component
762        data = GuiUtils.dataFromItem(self._model_item)
763        self.model.item(WIDGETS.W_FILENAME).setData(QtCore.QVariant(self._model_item.text()))
764        # update GUI and model with info from loaded data
765        self.updateGuiFromFile(data=data)
766
767    def updateGuiFromFile(self, data=None):
768        """
769        update display in GUI and plot
770        """
771        self._data = data
772
773        # plot loaded file
774        if not isinstance(self._data, Data1D):
775            msg = "Error(s) occurred: Invariant cannot be computed with 2D data."
776            raise AttributeError(msg)
777
778        try:
779            filename = data.filename
780        except:
781            msg = 'No filename'
782            raise ValueError(msg)
783        try:
784            qmin = min(self._data.x)
785            qmax = max(self._data.x)
786        except:
787            msg = "Unable to find q min/max of \n data named %s" % \
788                  data.filename
789            raise ValueError(msg)
790
791        # update model with input form files: filename, qmin, qmax
792        self.model.item(WIDGETS.W_FILENAME).setText(filename)
793        self.model.item(WIDGETS.W_QMIN).setText(str(qmin))
794        self.model.item(WIDGETS.W_QMAX).setText(str(qmax))
795        self._path = filename
796
797        # Calculate and add to GUI: volume fraction, invariant total,
798        # and specific surface if porod checked
799        self.calculateInvariant()
800
801    def allowBatch(self):
802        """
803        Tell the caller that we don't accept multiple data instances
804        """
805        return False
Note: See TracBrowser for help on using the repository browser.