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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 6ae7466 was 6ae7466, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Complain when wrong data sent to perspective. SASVIEW-1165 SASVIEW-1166

  • Property mode set to 100644
File size: 31.0 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
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
[7c487846]125        # Default enablement
126        self.cmdCalculate.setEnabled(False)
127
[f1f3e6a]128        # validator: double
[7c487846]129        self.txtBackgd.setValidator(GuiUtils.DoubleValidator())
130        self.txtContrast.setValidator(GuiUtils.DoubleValidator())
131        self.txtScale.setValidator(GuiUtils.DoubleValidator())
132        self.txtPorodCst.setValidator(GuiUtils.DoubleValidator())
[f1f3e6a]133
134        # validator: integer number
[7c487846]135        self.txtNptsLowQ.setValidator(QtGui.QIntValidator())
136        self.txtNptsHighQ.setValidator(QtGui.QIntValidator())
137        self.txtPowerLowQ.setValidator(GuiUtils.DoubleValidator())
138        self.txtPowerHighQ.setValidator(GuiUtils.DoubleValidator())
[f1f3e6a]139
140    def enabling(self):
141        """ """
142        self.cmdStatus.setEnabled(True)
[457d961]143
[b1e36a3]144    def setClosable(self, value=True):
[f1f3e6a]145        """ Allow outsiders close this widget """
[b1e36a3]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)
[7c487846]157            # Tell the MdiArea to close the container
158            self.parentWidget().close()
[b1e36a3]159            event.accept()
160        else:
161            event.ignore()
162            # Maybe we should just minimize
163            self.setWindowState(QtCore.Qt.WindowMinimized)
[e540cd2]164
[f721030]165    def updateFromModel(self):
[f1f3e6a]166        """ Update the globals based on the data in the model """
[f721030]167        self._background = float(self.model.item(WIDGETS.W_BACKGROUND).text())
[f1f3e6a]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'
[f721030]176        self._low_points = float(self.model.item(WIDGETS.W_NPTS_LOWQ).text())
[f1f3e6a]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())
[f721030]180
181        # High extrapolating
[f1f3e6a]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())
[f721030]186
[a281ab8]187    def calculateInvariant(self):
[f1f3e6a]188        """ Use twisted to thread the calculations away """
[f721030]189        # Find out if extrapolation needs to be used.
190        extrapolation = None
[f1f3e6a]191        if self._low_extrapolate and not self._high_extrapolate:
[f721030]192            extrapolation = "low"
[f1f3e6a]193        elif not self._low_extrapolate and self._high_extrapolate:
[f721030]194            extrapolation = "high"
195        elif self._low_extrapolate and self._high_extrapolate:
196            extrapolation = "both"
197
[f1f3e6a]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)
[7fb471d]211        pass
[5032ea68]212
[f1f3e6a]213    def deferredPlot(self, model):
[f721030]214        """
[f1f3e6a]215        Run the GUI/model update in the main thread
[f721030]216        """
[f1f3e6a]217        reactor.callFromThread(lambda: self.plotResult(model))
[f721030]218
[f1f3e6a]219    def plotResult(self, model):
220        """ Plot result of calculation """
[f721030]221        # Set the button back to available
[f1f3e6a]222        self.cmdCalculate.setEnabled(True)
223        self.cmdCalculate.setText("Calculate")
224        self.cmdStatus.setEnabled(True)
225
226        self.model = model
[9387fe3]227        self.mapper.toFirst()
[f1f3e6a]228        self._data = GuiUtils.dataFromItem(self._model_item)
[f721030]229
[a281ab8]230        # Send the modified model item to DE for keeping in the model
[f1f3e6a]231        # Currently -unused
[6cb305a]232        # self.communicate.updateModelFromPerspectiveSignal.emit(self._model_item)
[f1f3e6a]233
234        plot_data = GuiUtils.plotsFromCheckedItems(self._manager.filesWidget.model)
[9ce69ec]235        #self.communicate.plotRequestedSignal.emit([item, plot], self.tab_id)
[f1f3e6a]236
237        self._manager.filesWidget.plotData(plot_data)
[5032ea68]238
[f1f3e6a]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()
[f721030]248
249    def calculateThread(self, extrapolation):
250        """
[5032ea68]251        Perform Invariant calculations.
252        TODO: Create a dictionary of results to be sent to DE on completion.
[f721030]253        """
254        self.updateFromModel()
[f1f3e6a]255        msg = ''
[f721030]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
[f1f3e6a]262        qstar_total_error = 0.0
[f721030]263
[f1f3e6a]264        temp_data = copy.deepcopy(self._data)
[f721030]265
[f1f3e6a]266        # Prepare the invariant object
267        inv = invariant.InvariantCalculator(data=temp_data,
268                                            background=self._background,
269                                            scale=self._scale)
[f721030]270        if self._low_extrapolate:
[f1f3e6a]271
[f721030]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
[f1f3e6a]277
[f721030]278            inv.set_extrapolation(range="low",
[f1f3e6a]279                                  npts=int(self._low_points),
[f721030]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",
[f1f3e6a]286                                  npts=int(self._high_points),
[f721030]287                                  function=function_low,
[f1f3e6a]288                                  power=self._high_power_value)
289        # Compute invariant
[f721030]290        calculation_failed = False
[f1f3e6a]291
[f721030]292        try:
[f1f3e6a]293            qstar_total, qstar_total_error = inv.get_qstar_with_error()
[f721030]294        except Exception as ex:
[f1f3e6a]295            msg += str(ex)
[f721030]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)
[f1f3e6a]302
[f721030]303        try:
304            volume_fraction, volume_fraction_error = \
305                inv.get_volume_fraction_with_error(self._contrast)
[f1f3e6a]306
[f721030]307        except Exception as ex:
308            calculation_failed = True
[f1f3e6a]309            msg += str(ex)
[f721030]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
[f1f3e6a]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))
[f721030]333            return self.model
[f1f3e6a]334        else:
[f721030]335
[f1f3e6a]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
[9ce69ec]350                extrapolated_data.style = "Line"
351                extrapolated_data.has_errors = False
352                extrapolated_data.plot_role = Data1D.ROLE_DEFAULT
[f1f3e6a]353
354                # copy labels and units of axes for plotting
355                extrapolated_data._xaxis = temp_data._xaxis
356                extrapolated_data._xunit = temp_data._xunit
357                extrapolated_data._yaxis = temp_data._yaxis
358                extrapolated_data._yunit = temp_data._yunit
359
360                # Add the plot to the model item
361                # This needs to run in the main thread
362                reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
363                                       self._model_item,
364                                       extrapolated_data,
365                                       title)
366
367            if self._high_extrapolate:
368                # for presentation in InvariantDetails
[6cb305a]369                qmax_plot = Q_MAXIMUM_PLOT * max(temp_data.x)
[f1f3e6a]370
371                if qmax_plot > Q_MAXIMUM:
372                    qmax_plot = Q_MAXIMUM
373                qstar_high, qstar_high_err = inv.get_qstar_high()
374                power_high = inv.get_extrapolation_power(range='high')
375                high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
376
377                # Plot the chart
378                title = "High-Q extrapolation"
379
380                # Convert the data into plottable
381                high_out_data = self._manager.createGuiData(high_out_data)
382                high_out_data.name = title
383                high_out_data.title = title
[9ce69ec]384                high_out_data.style = "Line"
385                high_out_data.has_errors = False
386                high_out_data.plot_role = Data1D.ROLE_DEFAULT
[f1f3e6a]387
388                # copy labels and units of axes for plotting
389                high_out_data._xaxis = temp_data._xaxis
390                high_out_data._xunit = temp_data._xunit
391                high_out_data._yaxis = temp_data._yaxis
392                high_out_data._yunit = temp_data._yunit
393
394                # Add the plot to the model item
395                # This needs to run in the main thread
396                reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
397                                       self._model_item, high_out_data, title)
398
399            item = QtGui.QStandardItem(str(float('%.3g'% volume_fraction)))
400            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
401            item = QtGui.QStandardItem(str(float('%.3g'% volume_fraction_error)))
402            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
403            if surface:
404                item = QtGui.QStandardItem(str(float('%.3g'% surface)))
405                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
406                item = QtGui.QStandardItem(str(float('%.3g'% surface_error)))
407                self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
408            item = QtGui.QStandardItem(str(float('%.3g'% qstar_total)))
409            self.model.setItem(WIDGETS.W_INVARIANT, item)
410            item = QtGui.QStandardItem(str(float('%.3g'% qstar_total_error)))
411            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
[1042dba]412
[f1f3e6a]413            item = QtGui.QStandardItem(str(float('%.3g'% qstar_low)))
414            self.model.setItem(WIDGETS.D_LOW_QSTAR, item)
415            item = QtGui.QStandardItem(str(float('%.3g'% qstar_low_err)))
416            self.model.setItem(WIDGETS.D_LOW_QSTAR_ERR, item)
417            item = QtGui.QStandardItem(str(float('%.3g'% qstar_high)))
418            self.model.setItem(WIDGETS.D_HIGH_QSTAR, item)
419            item = QtGui.QStandardItem(str(float('%.3g'% qstar_high_err)))
420            self.model.setItem(WIDGETS.D_HIGH_QSTAR_ERR, item)
[f721030]421
[f1f3e6a]422            return self.model
[f721030]423
[5032ea68]424    def title(self):
[f1f3e6a]425        """ Perspective name """
[5032ea68]426        return "Invariant panel"
[f721030]427
[f1f3e6a]428    def onStatus(self):
[f721030]429        """
[f1f3e6a]430        Display Invariant Details panel when clicking on Status button
[f721030]431        """
432        self.detailsDialog.setModel(self.model)
433        self.detailsDialog.showDialog()
[f1f3e6a]434        self.cmdStatus.setEnabled(False)
[f721030]435
[f1f3e6a]436    def onHelp(self):
437        """ Display help when clicking on Help button """
[aed0532]438        treeLocation = "/user/qtgui/Perspectives/Invariant/invariant_help.html"
[e90988c]439        self.parent.showHelp(treeLocation)
[f721030]440
441    def setupSlots(self):
[f1f3e6a]442        """ """
443        self.cmdCalculate.clicked.connect(self.calculateInvariant)
444        self.cmdStatus.clicked.connect(self.onStatus)
445        self.cmdHelp.clicked.connect(self.onHelp)
[f721030]446
[f1f3e6a]447        self.chkLowQ.stateChanged.connect(self.stateChanged)
448        self.chkLowQ.stateChanged.connect(self.checkQExtrapolatedData)
[f721030]449
[f1f3e6a]450        self.chkHighQ.stateChanged.connect(self.stateChanged)
451        self.chkHighQ.stateChanged.connect(self.checkQExtrapolatedData)
[f721030]452
[f1f3e6a]453        # slots for the Guinier and PowerLaw radio buttons at low Q
454        # since they are not auto-exclusive
455        self.rbGuinier.toggled.connect(self.lowGuinierAndPowerToggle)
456
457        self.rbPowerLawLowQ.toggled.connect(self.lowGuinierAndPowerToggle)
458
459        self.rbFitHighQ.toggled.connect(self.hiFitAndFixToggle)
460
461        self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
[f721030]462
463        self.model.itemChanged.connect(self.modelChanged)
464
[f1f3e6a]465        # update model from gui editing by users
466        self.txtBackgd.textChanged.connect(self.updateFromGui)
467
468        self.txtScale.textChanged.connect(self.updateFromGui)
469
470        self.txtContrast.textChanged.connect(self.updateFromGui)
471
472        self.txtPorodCst.textChanged.connect(self.updateFromGui)
473
474        self.txtPowerLowQ.textChanged.connect(self.updateFromGui)
475
476        self.txtPowerHighQ.textChanged.connect(self.updateFromGui)
477
478        self.txtNptsLowQ.textChanged.connect(self.updateFromGui)
479
480        self.txtNptsHighQ.textChanged.connect(self.updateFromGui)
481
482        # check values of n_points compared to distribution length
483        if self.txtNptsLowQ.isEnabled():
484            self.txtNptsLowQ.textChanged.connect(self.checkLength)
485
486        if self.txtNptsHighQ.isEnabled():
487            self.txtNptsHighQ.textChanged.connect(self.checkLength)
488
489    def stateChanged(self):
[f721030]490        """
[f1f3e6a]491        Catch modifications from low- and high-Q extrapolation check boxes
[f721030]492        """
[f1f3e6a]493        sender = self.sender()
494
495        itemf = QtGui.QStandardItem(str(sender.isChecked()).lower())
496        if sender.text() == 'Enable Low-Q extrapolation':
497            self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
498
499        if sender.text() == 'Enable High-Q extrapolation':
500            self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
501
502    def checkLength(self):
503        """
504        Validators of number of points for extrapolation.
505        Error if it is larger than the distribution length
506        """
[7c487846]507        try:
508            int_value = int(self.sender().text())
509        except ValueError:
510            self.sender().setStyleSheet(BG_RED)
511            self.cmdCalculate.setEnabled(False)
512            return
513
[f1f3e6a]514        if self._data:
[7c487846]515            if len(self._data.x) < int_value:
516                self.sender().setStyleSheet(BG_RED)
[f1f3e6a]517                logging.warning('The number of points must be smaller than {}'.format(len(self._data.x)))
518                self.cmdCalculate.setEnabled(False)
519            else:
[7c487846]520                self.sender().setStyleSheet(BG_WHITE)
[f1f3e6a]521                self.cmdCalculate.setEnabled(True)
522        else:
523            # logging.info('no data is loaded')
524            self.cmdCalculate.setEnabled(False)
525
526    def modelChanged(self, item):
527        """ Update when model changed """
[f721030]528        if item.row() == WIDGETS.W_ENABLE_LOWQ:
529            toggle = (str(item.text()) == 'true')
530            self._low_extrapolate = toggle
531            self.lowQToggle(toggle)
532        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
533            toggle = (str(item.text()) == 'true')
534            self._high_extrapolate = toggle
535            self.highQToggle(toggle)
[f1f3e6a]536
537    def checkQExtrapolatedData(self):
[f721030]538        """
[f1f3e6a]539        Match status of low or high-Q extrapolated data checkbox in
540        DataExplorer with low or high-Q extrapolation checkbox in invariant
541        panel
[f721030]542        """
[f1f3e6a]543        # name to search in DataExplorer
544        if 'Low' in str(self.sender().text()):
545            name = "Low-Q extrapolation"
546        if 'High' in str(self.sender().text()):
547            name = "High-Q extrapolation"
[f721030]548
[f1f3e6a]549        GuiUtils.updateModelItemStatus(self._manager.filesWidget.model,
550                                       self._path, name,
551                                       self.sender().checkState())
552
553    def updateFromGui(self):
554        """ Update model when new user inputs """
555        possible_senders = ['txtBackgd', 'txtContrast', 'txtPorodCst',
556                            'txtScale', 'txtPowerLowQ', 'txtPowerHighQ',
557                            'txtNptsLowQ', 'txtNptsHighQ']
558
559        related_widgets = [WIDGETS.W_BACKGROUND, WIDGETS.W_CONTRAST,
560                           WIDGETS.W_POROD_CST, WIDGETS.W_SCALE,
561                           WIDGETS.W_LOWQ_POWER_VALUE, WIDGETS.W_HIGHQ_POWER_VALUE,
562                           WIDGETS.W_NPTS_LOWQ, WIDGETS.W_NPTS_HIGHQ]
563
564        related_internal_values = [self._background, self._contrast,
565                                   self._porod, self._scale,
566                                   self._low_power_value,
567                                   self._high_power_value,
568                                   self._low_points, self._high_points]
569
570        item = QtGui.QStandardItem(self.sender().text())
571
572        index_elt = possible_senders.index(self.sender().objectName())
573
574        self.model.setItem(related_widgets[index_elt], item)
[7c487846]575        try:
576            related_internal_values[index_elt] = float(self.sender().text())
577            self.sender().setStyleSheet(BG_WHITE)
578            self.cmdCalculate.setEnabled(True)
579        except ValueError:
580            # empty field, just skip
581            self.sender().setStyleSheet(BG_RED)
582            self.cmdCalculate.setEnabled(False)
[f1f3e6a]583
584    def lowGuinierAndPowerToggle(self, toggle):
[f721030]585        """
[f1f3e6a]586        Guinier and Power radio buttons cannot be selected at the same time
587        If Power is selected, Fit and Fix radio buttons are visible and
588        Power line edit can be edited if Fix is selected
[f721030]589        """
[f1f3e6a]590        if self.sender().text() == 'Guinier':
591            self._low_guinier = toggle
592
593            toggle = not toggle
594            self.rbPowerLawLowQ.setChecked(toggle)
595
596            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
597            self.rbFitLowQ.setVisible(toggle)
598            self.rbFixLowQ.setVisible(toggle)
599
600            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
601
602        else:
603            self._low_guinier = not toggle
604
605            self.rbGuinier.setChecked(not toggle)
606
607            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
608            self.rbFitLowQ.setVisible(toggle)
609            self.rbFixLowQ.setVisible(toggle)
610
611            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
612
613    def lowFitAndFixToggle(self, toggle):
614        """ Fit and Fix radiobuttons cannot be selected at the same time """
[f721030]615        self._low_fit = toggle
[f1f3e6a]616
[f721030]617        toggle = not toggle
[f1f3e6a]618        self.txtPowerLowQ.setEnabled(toggle)
[f721030]619
620    def hiFitAndFixToggle(self, toggle):
621        """
[f1f3e6a]622        Enable editing of power exponent if Fix for high Q is checked
623        Disable otherwise
[f721030]624        """
[f1f3e6a]625        self.txtPowerHighQ.setEnabled(not toggle)
[f721030]626
627    def highQToggle(self, clicked):
[f1f3e6a]628        """ Disable/enable High Q extrapolation """
629        self.rbFitHighQ.setEnabled(clicked)
630        self.rbFixHighQ.setEnabled(clicked)
631        self.txtNptsHighQ.setEnabled(clicked)
632        self.txtPowerHighQ.setEnabled(clicked)
[f721030]633
634    def lowQToggle(self, clicked):
[f1f3e6a]635        """ Disable / enable Low Q extrapolation """
636        self.rbGuinier.setEnabled(clicked)
637        self.rbPowerLawLowQ.setEnabled(clicked)
638        self.txtNptsLowQ.setEnabled(clicked)
[f721030]639        # Enable subelements
[f1f3e6a]640        self.rbFitLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
641        self.rbFixLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
642        self.rbFitLowQ.setEnabled(clicked)  # and not self._low_guinier)
643        self.rbFixLowQ.setEnabled(clicked)  # and not self._low_guinier)
[f721030]644
[f1f3e6a]645        self.txtPowerLowQ.setEnabled(clicked
646                                    and not self._low_guinier
647                                    and not self._low_fit)
[f721030]648
[f1f3e6a]649    def setupModel(self):
650        """ """
[f721030]651        # filename
652        item = QtGui.QStandardItem(self._path)
653        self.model.setItem(WIDGETS.W_FILENAME, item)
654
655        # add Q parameters to the model
[469b4622]656        qmin = 0.0
[f721030]657        item = QtGui.QStandardItem(str(qmin))
658        self.model.setItem(WIDGETS.W_QMIN, item)
[469b4622]659        qmax = 0.0
660        item = QtGui.QStandardItem(str(qmax))
[f721030]661        self.model.setItem(WIDGETS.W_QMAX, item)
662
663        # add custom input params
664        item = QtGui.QStandardItem(str(self._background))
665        self.model.setItem(WIDGETS.W_BACKGROUND, item)
666        item = QtGui.QStandardItem(str(self._contrast))
667        self.model.setItem(WIDGETS.W_CONTRAST, item)
668        item = QtGui.QStandardItem(str(self._scale))
669        self.model.setItem(WIDGETS.W_SCALE, item)
[f1f3e6a]670        # leave line edit empty if Porod constant not defined
671        if self._porod != None:
672            item = QtGui.QStandardItem(str(self._porod))
673        else:
674            item = QtGui.QStandardItem(str(''))
675        self.model.setItem(WIDGETS.W_POROD_CST, item)
676
[f721030]677        # Dialog elements
678        itemf = QtGui.QStandardItem("false")
679        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
680        itemf = QtGui.QStandardItem("false")
681        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
682
[f1f3e6a]683        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
[f721030]684        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
[f1f3e6a]685        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
[f721030]686        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
687
688        itemt = QtGui.QStandardItem("true")
689        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
690
691        itemt = QtGui.QStandardItem("true")
692        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
[f1f3e6a]693        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
[f721030]694        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
695
696        itemt = QtGui.QStandardItem("true")
697        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
[f1f3e6a]698        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
[f721030]699        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
700
701    def setupMapper(self):
702        # Set up the mapper.
[4992ff2]703        self.mapper = QtWidgets.QDataWidgetMapper(self)
[f721030]704        self.mapper.setOrientation(QtCore.Qt.Vertical)
705        self.mapper.setModel(self.model)
706
707        # Filename
[f1f3e6a]708        self.mapper.addMapping(self.txtName, WIDGETS.W_FILENAME)
709
[f721030]710        # Qmin/Qmax
[f1f3e6a]711        self.mapper.addMapping(self.txtTotalQMin, WIDGETS.W_QMIN)
712        self.mapper.addMapping(self.txtTotalQMax, WIDGETS.W_QMAX)
[f721030]713
714        # Background
[f1f3e6a]715        self.mapper.addMapping(self.txtBackgd, WIDGETS.W_BACKGROUND)
716
[f721030]717        # Scale
[f1f3e6a]718        self.mapper.addMapping(self.txtScale, WIDGETS.W_SCALE)
719
[f721030]720        # Contrast
[f1f3e6a]721        self.mapper.addMapping(self.txtContrast, WIDGETS.W_CONTRAST)
[f721030]722
[f1f3e6a]723        # Porod constant
724        self.mapper.addMapping(self.txtPorodCst, WIDGETS.W_POROD_CST)
[f721030]725
[f1f3e6a]726        # Lowq/Highq items
727        self.mapper.addMapping(self.chkLowQ, WIDGETS.W_ENABLE_LOWQ)
728        self.mapper.addMapping(self.chkHighQ, WIDGETS.W_ENABLE_HIGHQ)
[f721030]729
[f1f3e6a]730        self.mapper.addMapping(self.txtNptsLowQ, WIDGETS.W_NPTS_LOWQ)
731        self.mapper.addMapping(self.rbGuinier, WIDGETS.W_LOWQ_GUINIER)
732        self.mapper.addMapping(self.rbFitLowQ, WIDGETS.W_LOWQ_FIT)
733        self.mapper.addMapping(self.txtPowerLowQ, WIDGETS.W_LOWQ_POWER_VALUE)
[f721030]734
[f1f3e6a]735        self.mapper.addMapping(self.txtNptsHighQ, WIDGETS.W_NPTS_HIGHQ)
736        self.mapper.addMapping(self.rbFitHighQ, WIDGETS.W_HIGHQ_FIT)
737        self.mapper.addMapping(self.txtPowerHighQ, WIDGETS.W_HIGHQ_POWER_VALUE)
[f721030]738   
739        # Output
[f1f3e6a]740        self.mapper.addMapping(self.txtVolFract, WIDGETS.W_VOLUME_FRACTION)
741        self.mapper.addMapping(self.txtVolFractErr, WIDGETS.W_VOLUME_FRACTION_ERR)
742        self.mapper.addMapping(self.txtSpecSurf, WIDGETS.W_SPECIFIC_SURFACE)
743        self.mapper.addMapping(self.txtSpecSurfErr, WIDGETS.W_SPECIFIC_SURFACE_ERR)
744        self.mapper.addMapping(self.txtInvariantTot, WIDGETS.W_INVARIANT)
745        self.mapper.addMapping(self.txtInvariantTotErr, WIDGETS.W_INVARIANT_ERR)
[f721030]746
[f1f3e6a]747        self.mapper.toFirst()
[f721030]748
[f1f3e6a]749    def setData(self, data_item=None, is_batch=False):
[a281ab8]750        """
751        Obtain a QStandardItem object and dissect it to get Data1D/2D
752        Pass it over to the calculator
753        """
[f1f3e6a]754        assert data_item is not None
755
756        if self.txtName.text() == data_item[0].text():
757            logging.info('This file is already loaded in Invariant panel.')
758            return
759
[a281ab8]760        if not isinstance(data_item, list):
761            msg = "Incorrect type passed to the Invariant Perspective"
[b3e8629]762            raise AttributeError(msg)
[a281ab8]763
764        if not isinstance(data_item[0], QtGui.QStandardItem):
765            msg = "Incorrect type passed to the Invariant Perspective"
[b3e8629]766            raise AttributeError(msg)
[a281ab8]767
[f1f3e6a]768        # only 1 file can be loaded
[a281ab8]769        self._model_item = data_item[0]
770
771        # Extract data on 1st child - this is the Data1D/2D component
[8548d739]772        data = GuiUtils.dataFromItem(self._model_item)
[7c487846]773        self.model.item(WIDGETS.W_FILENAME).setData(self._model_item.text())
[f1f3e6a]774        # update GUI and model with info from loaded data
775        self.updateGuiFromFile(data=data)
[a281ab8]776
[f1f3e6a]777    def updateGuiFromFile(self, data=None):
[f721030]778        """
[f1f3e6a]779        update display in GUI and plot
[f721030]780        """
[f1f3e6a]781        self._data = data
782
783        # plot loaded file
784        if not isinstance(self._data, Data1D):
[6ae7466]785            msg = "Invariant cannot be computed with 2D data."
786            raise ValueError(msg)
[f1f3e6a]787
788        try:
789            filename = data.filename
790        except:
[6ae7466]791            msg = 'No filename chosen.'
[f1f3e6a]792            raise ValueError(msg)
793        try:
794            qmin = min(self._data.x)
795            qmax = max(self._data.x)
796        except:
797            msg = "Unable to find q min/max of \n data named %s" % \
798                  data.filename
799            raise ValueError(msg)
800
801        # update model with input form files: filename, qmin, qmax
802        self.model.item(WIDGETS.W_FILENAME).setText(filename)
803        self.model.item(WIDGETS.W_QMIN).setText(str(qmin))
804        self.model.item(WIDGETS.W_QMAX).setText(str(qmax))
805        self._path = filename
806
807        # Calculate and add to GUI: volume fraction, invariant total,
808        # and specific surface if porod checked
809        self.calculateInvariant()
[f721030]810
[5032ea68]811    def allowBatch(self):
812        """
813        Tell the caller that we don't accept multiple data instances
814        """
815        return False
Note: See TracBrowser for help on using the repository browser.