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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 42d79fc was 42d79fc, checked in by wojciech, 6 years ago

Disabling reporting for perspectives other than fitting

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