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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_sync_sascalc
Last change on this file since 11a336f was 11a336f, checked in by wojciech, 5 years ago

Fixing plot presentation in Invariant Perspective

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