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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since 5d14851a was 4cdfa17, checked in by wojciech, 6 years ago

Moving qt parts to main thread in Invariant perspective

  • 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
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
[1dc1d431]331        if (calculation_failed):
332            logging.warning('Calculation failed: {}'.format(msg))
333            return self.model
334
335        low_calculation_pass = True
336        high_calculation_pass = True
[740c381]337        if self._low_extrapolate:
338            try:
339                qstar_low, qstar_low_err = inv.get_qstar_low()
340            except Exception as ex:
[1dc1d431]341                low_calculation_pass = False
[740c381]342                msg += str(ex)
[1dc1d431]343                logging.warning('Low-q calculation failed: {}'.format(msg))
[740c381]344        if self._high_extrapolate:
345            try:
346                qstar_high, qstar_high_err = inv.get_qstar_high()
347            except Exception as ex:
[1dc1d431]348                high_calculation_pass = False
[740c381]349                msg += str(ex)
[1dc1d431]350                logging.warning('High-q calculation failed: {}'.format(msg))
[740c381]351
[aea6bb7]352
[1dc1d431]353        if self._low_extrapolate and low_calculation_pass:
[aea6bb7]354            extrapolated_data = inv.get_extra_data_low(self._low_points)
355            power_low = inv.get_extrapolation_power(range='low')
356
357            # Plot the chart
358            title = "Low-Q extrapolation"
359
360            # Convert the data into plottable
361            extrapolated_data = self._manager.createGuiData(extrapolated_data)
362
363            extrapolated_data.name = title
364            extrapolated_data.title = title
365            extrapolated_data.symbol = "Line"
366            extrapolated_data.has_errors = False
367
368            # copy labels and units of axes for plotting
369            extrapolated_data._xaxis = temp_data._xaxis
370            extrapolated_data._xunit = temp_data._xunit
371            extrapolated_data._yaxis = temp_data._yaxis
372            extrapolated_data._yunit = temp_data._yunit
373
374            # Add the plot to the model item
375            # This needs to run in the main thread
376            reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
[f1f3e6a]377                                       self._model_item,
378                                       extrapolated_data,
379                                       title)
380
[1dc1d431]381        if self._high_extrapolate and high_calculation_pass:
[aea6bb7]382            # for presentation in InvariantDetails
383            qmax_plot = Q_MAXIMUM_PLOT * max(temp_data.x)
384
385            if qmax_plot > Q_MAXIMUM:
386                qmax_plot = Q_MAXIMUM
387            power_high = inv.get_extrapolation_power(range='high')
388            high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
389
390            # Plot the chart
391            title = "High-Q extrapolation"
392
393            # Convert the data into plottable
394            high_out_data = self._manager.createGuiData(high_out_data)
395            high_out_data.name = title
396            high_out_data.title = title
397            high_out_data.symbol = "Line"
398            high_out_data.has_errors = False
399
400            # copy labels and units of axes for plotting
401            high_out_data._xaxis = temp_data._xaxis
402            high_out_data._xunit = temp_data._xunit
403            high_out_data._yaxis = temp_data._yaxis
404            high_out_data._yunit = temp_data._yunit
405
406            # Add the plot to the model item
407            # This needs to run in the main thread
408            reactor.callFromThread(GuiUtils.updateModelItemWithPlot,
[f1f3e6a]409                                       self._model_item, high_out_data, title)
410
[4cdfa17]411        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_VOLUME_FRACTION, volume_fraction)
412        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_VOLUME_FRACTION_ERR, volume_fraction_error)
[aea6bb7]413        if surface:
[4cdfa17]414            reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_SPECIFIC_SURFACE, surface)
415            reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_SPECIFIC_SURFACE_ERR,
416                                                    surface_error)
417
418        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_INVARIANT, qstar_total)
419        reactor.callFromThread(self.updateModelFromThread, WIDGETS.W_INVARIANT_ERR, qstar_total_error)
420        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_LOW_QSTAR, qstar_low)
421        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_LOW_QSTAR_ERR, qstar_low_err)
422        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_HIGH_QSTAR, qstar_high)
423        reactor.callFromThread(self.updateModelFromThread, WIDGETS.D_HIGH_QSTAR_ERR, qstar_high_err)
[aea6bb7]424        return self.model
[f721030]425
[4cdfa17]426
427    def updateModelFromThread(self, widget, value):
428        """
429        Update the model in the main thread
430        """
431        item = QtGui.QStandardItem(str(float('%.3g' % value)))
432        self.model.setItem(widget, item)
433
[5032ea68]434    def title(self):
[f1f3e6a]435        """ Perspective name """
[5032ea68]436        return "Invariant panel"
[f721030]437
[f1f3e6a]438    def onStatus(self):
[f721030]439        """
[f1f3e6a]440        Display Invariant Details panel when clicking on Status button
[f721030]441        """
442        self.detailsDialog.setModel(self.model)
443        self.detailsDialog.showDialog()
[f1f3e6a]444        self.cmdStatus.setEnabled(False)
[f721030]445
[f1f3e6a]446    def onHelp(self):
447        """ Display help when clicking on Help button """
[aed0532]448        treeLocation = "/user/qtgui/Perspectives/Invariant/invariant_help.html"
[e90988c]449        self.parent.showHelp(treeLocation)
[f721030]450
451    def setupSlots(self):
[f1f3e6a]452        """ """
453        self.cmdCalculate.clicked.connect(self.calculateInvariant)
454        self.cmdStatus.clicked.connect(self.onStatus)
455        self.cmdHelp.clicked.connect(self.onHelp)
[f721030]456
[f1f3e6a]457        self.chkLowQ.stateChanged.connect(self.stateChanged)
458        self.chkLowQ.stateChanged.connect(self.checkQExtrapolatedData)
[f721030]459
[f1f3e6a]460        self.chkHighQ.stateChanged.connect(self.stateChanged)
461        self.chkHighQ.stateChanged.connect(self.checkQExtrapolatedData)
[f721030]462
[f1f3e6a]463        # slots for the Guinier and PowerLaw radio buttons at low Q
464        # since they are not auto-exclusive
465        self.rbGuinier.toggled.connect(self.lowGuinierAndPowerToggle)
466
467        self.rbPowerLawLowQ.toggled.connect(self.lowGuinierAndPowerToggle)
468
469        self.rbFitHighQ.toggled.connect(self.hiFitAndFixToggle)
470
471        self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
[f721030]472
473        self.model.itemChanged.connect(self.modelChanged)
474
[f1f3e6a]475        # update model from gui editing by users
476        self.txtBackgd.textChanged.connect(self.updateFromGui)
477
478        self.txtScale.textChanged.connect(self.updateFromGui)
479
480        self.txtContrast.textChanged.connect(self.updateFromGui)
481
482        self.txtPorodCst.textChanged.connect(self.updateFromGui)
483
484        self.txtPowerLowQ.textChanged.connect(self.updateFromGui)
485
486        self.txtPowerHighQ.textChanged.connect(self.updateFromGui)
487
488        self.txtNptsLowQ.textChanged.connect(self.updateFromGui)
489
490        self.txtNptsHighQ.textChanged.connect(self.updateFromGui)
491
492        # check values of n_points compared to distribution length
493        if self.txtNptsLowQ.isEnabled():
494            self.txtNptsLowQ.textChanged.connect(self.checkLength)
495
496        if self.txtNptsHighQ.isEnabled():
497            self.txtNptsHighQ.textChanged.connect(self.checkLength)
498
499    def stateChanged(self):
[f721030]500        """
[f1f3e6a]501        Catch modifications from low- and high-Q extrapolation check boxes
[f721030]502        """
[f1f3e6a]503        sender = self.sender()
504
505        itemf = QtGui.QStandardItem(str(sender.isChecked()).lower())
506        if sender.text() == 'Enable Low-Q extrapolation':
507            self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
508
509        if sender.text() == 'Enable High-Q extrapolation':
510            self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
511
512    def checkLength(self):
513        """
514        Validators of number of points for extrapolation.
515        Error if it is larger than the distribution length
516        """
[7c487846]517        try:
518            int_value = int(self.sender().text())
519        except ValueError:
520            self.sender().setStyleSheet(BG_RED)
521            self.cmdCalculate.setEnabled(False)
522            return
523
[f1f3e6a]524        if self._data:
[7c487846]525            if len(self._data.x) < int_value:
526                self.sender().setStyleSheet(BG_RED)
[f1f3e6a]527                logging.warning('The number of points must be smaller than {}'.format(len(self._data.x)))
528                self.cmdCalculate.setEnabled(False)
529            else:
[7c487846]530                self.sender().setStyleSheet(BG_WHITE)
[f1f3e6a]531                self.cmdCalculate.setEnabled(True)
532        else:
533            # logging.info('no data is loaded')
534            self.cmdCalculate.setEnabled(False)
535
536    def modelChanged(self, item):
537        """ Update when model changed """
[f721030]538        if item.row() == WIDGETS.W_ENABLE_LOWQ:
539            toggle = (str(item.text()) == 'true')
540            self._low_extrapolate = toggle
541            self.lowQToggle(toggle)
542        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
543            toggle = (str(item.text()) == 'true')
544            self._high_extrapolate = toggle
545            self.highQToggle(toggle)
[f1f3e6a]546
547    def checkQExtrapolatedData(self):
[f721030]548        """
[f1f3e6a]549        Match status of low or high-Q extrapolated data checkbox in
550        DataExplorer with low or high-Q extrapolation checkbox in invariant
551        panel
[f721030]552        """
[f1f3e6a]553        # name to search in DataExplorer
554        if 'Low' in str(self.sender().text()):
555            name = "Low-Q extrapolation"
556        if 'High' in str(self.sender().text()):
557            name = "High-Q extrapolation"
[f721030]558
[f1f3e6a]559        GuiUtils.updateModelItemStatus(self._manager.filesWidget.model,
560                                       self._path, name,
561                                       self.sender().checkState())
562
563    def updateFromGui(self):
564        """ Update model when new user inputs """
565        possible_senders = ['txtBackgd', 'txtContrast', 'txtPorodCst',
566                            'txtScale', 'txtPowerLowQ', 'txtPowerHighQ',
567                            'txtNptsLowQ', 'txtNptsHighQ']
568
569        related_widgets = [WIDGETS.W_BACKGROUND, WIDGETS.W_CONTRAST,
570                           WIDGETS.W_POROD_CST, WIDGETS.W_SCALE,
571                           WIDGETS.W_LOWQ_POWER_VALUE, WIDGETS.W_HIGHQ_POWER_VALUE,
572                           WIDGETS.W_NPTS_LOWQ, WIDGETS.W_NPTS_HIGHQ]
573
574        related_internal_values = [self._background, self._contrast,
575                                   self._porod, self._scale,
576                                   self._low_power_value,
577                                   self._high_power_value,
578                                   self._low_points, self._high_points]
579
580        item = QtGui.QStandardItem(self.sender().text())
581
582        index_elt = possible_senders.index(self.sender().objectName())
583
584        self.model.setItem(related_widgets[index_elt], item)
[7c487846]585        try:
586            related_internal_values[index_elt] = float(self.sender().text())
587            self.sender().setStyleSheet(BG_WHITE)
588            self.cmdCalculate.setEnabled(True)
589        except ValueError:
590            # empty field, just skip
591            self.sender().setStyleSheet(BG_RED)
592            self.cmdCalculate.setEnabled(False)
[f1f3e6a]593
594    def lowGuinierAndPowerToggle(self, toggle):
[f721030]595        """
[f1f3e6a]596        Guinier and Power radio buttons cannot be selected at the same time
597        If Power is selected, Fit and Fix radio buttons are visible and
598        Power line edit can be edited if Fix is selected
[f721030]599        """
[f1f3e6a]600        if self.sender().text() == 'Guinier':
601            self._low_guinier = toggle
602
603            toggle = not toggle
604            self.rbPowerLawLowQ.setChecked(toggle)
605
606            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
607            self.rbFitLowQ.setVisible(toggle)
608            self.rbFixLowQ.setVisible(toggle)
609
610            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
611
612        else:
613            self._low_guinier = not toggle
614
615            self.rbGuinier.setChecked(not toggle)
616
617            self.rbFitLowQ.toggled.connect(self.lowFitAndFixToggle)
618            self.rbFitLowQ.setVisible(toggle)
619            self.rbFixLowQ.setVisible(toggle)
620
621            self.txtPowerLowQ.setEnabled(toggle and (not self._low_fit))
622
623    def lowFitAndFixToggle(self, toggle):
624        """ Fit and Fix radiobuttons cannot be selected at the same time """
[f721030]625        self._low_fit = toggle
[f1f3e6a]626
[f721030]627        toggle = not toggle
[f1f3e6a]628        self.txtPowerLowQ.setEnabled(toggle)
[f721030]629
630    def hiFitAndFixToggle(self, toggle):
631        """
[f1f3e6a]632        Enable editing of power exponent if Fix for high Q is checked
633        Disable otherwise
[f721030]634        """
[f1f3e6a]635        self.txtPowerHighQ.setEnabled(not toggle)
[f721030]636
637    def highQToggle(self, clicked):
[f1f3e6a]638        """ Disable/enable High Q extrapolation """
639        self.rbFitHighQ.setEnabled(clicked)
640        self.rbFixHighQ.setEnabled(clicked)
641        self.txtNptsHighQ.setEnabled(clicked)
642        self.txtPowerHighQ.setEnabled(clicked)
[f721030]643
644    def lowQToggle(self, clicked):
[f1f3e6a]645        """ Disable / enable Low Q extrapolation """
646        self.rbGuinier.setEnabled(clicked)
647        self.rbPowerLawLowQ.setEnabled(clicked)
648        self.txtNptsLowQ.setEnabled(clicked)
[f721030]649        # Enable subelements
[f1f3e6a]650        self.rbFitLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
651        self.rbFixLowQ.setVisible(self.rbPowerLawLowQ.isChecked())
652        self.rbFitLowQ.setEnabled(clicked)  # and not self._low_guinier)
653        self.rbFixLowQ.setEnabled(clicked)  # and not self._low_guinier)
[f721030]654
[f1f3e6a]655        self.txtPowerLowQ.setEnabled(clicked
656                                    and not self._low_guinier
657                                    and not self._low_fit)
[f721030]658
[f1f3e6a]659    def setupModel(self):
660        """ """
[f721030]661        # filename
662        item = QtGui.QStandardItem(self._path)
663        self.model.setItem(WIDGETS.W_FILENAME, item)
664
665        # add Q parameters to the model
[469b4622]666        qmin = 0.0
[f721030]667        item = QtGui.QStandardItem(str(qmin))
668        self.model.setItem(WIDGETS.W_QMIN, item)
[469b4622]669        qmax = 0.0
670        item = QtGui.QStandardItem(str(qmax))
[f721030]671        self.model.setItem(WIDGETS.W_QMAX, item)
672
673        # add custom input params
674        item = QtGui.QStandardItem(str(self._background))
675        self.model.setItem(WIDGETS.W_BACKGROUND, item)
676        item = QtGui.QStandardItem(str(self._contrast))
677        self.model.setItem(WIDGETS.W_CONTRAST, item)
678        item = QtGui.QStandardItem(str(self._scale))
679        self.model.setItem(WIDGETS.W_SCALE, item)
[f1f3e6a]680        # leave line edit empty if Porod constant not defined
681        if self._porod != None:
682            item = QtGui.QStandardItem(str(self._porod))
683        else:
684            item = QtGui.QStandardItem(str(''))
685        self.model.setItem(WIDGETS.W_POROD_CST, item)
686
[f721030]687        # Dialog elements
688        itemf = QtGui.QStandardItem("false")
689        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
690        itemf = QtGui.QStandardItem("false")
691        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
692
[f1f3e6a]693        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
[f721030]694        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
[f1f3e6a]695        item = QtGui.QStandardItem(str(NPOINTS_Q_INTERP))
[f721030]696        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
697
698        itemt = QtGui.QStandardItem("true")
699        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
700
701        itemt = QtGui.QStandardItem("true")
702        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
[f1f3e6a]703        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
[f721030]704        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
705
706        itemt = QtGui.QStandardItem("true")
707        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
[f1f3e6a]708        item = QtGui.QStandardItem(str(DEFAULT_POWER_LOW))
[f721030]709        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
710
711    def setupMapper(self):
712        # Set up the mapper.
[4992ff2]713        self.mapper = QtWidgets.QDataWidgetMapper(self)
[f721030]714        self.mapper.setOrientation(QtCore.Qt.Vertical)
715        self.mapper.setModel(self.model)
716
717        # Filename
[f1f3e6a]718        self.mapper.addMapping(self.txtName, WIDGETS.W_FILENAME)
719
[f721030]720        # Qmin/Qmax
[f1f3e6a]721        self.mapper.addMapping(self.txtTotalQMin, WIDGETS.W_QMIN)
722        self.mapper.addMapping(self.txtTotalQMax, WIDGETS.W_QMAX)
[f721030]723
724        # Background
[f1f3e6a]725        self.mapper.addMapping(self.txtBackgd, WIDGETS.W_BACKGROUND)
726
[f721030]727        # Scale
[f1f3e6a]728        self.mapper.addMapping(self.txtScale, WIDGETS.W_SCALE)
729
[f721030]730        # Contrast
[f1f3e6a]731        self.mapper.addMapping(self.txtContrast, WIDGETS.W_CONTRAST)
[f721030]732
[f1f3e6a]733        # Porod constant
734        self.mapper.addMapping(self.txtPorodCst, WIDGETS.W_POROD_CST)
[f721030]735
[f1f3e6a]736        # Lowq/Highq items
737        self.mapper.addMapping(self.chkLowQ, WIDGETS.W_ENABLE_LOWQ)
738        self.mapper.addMapping(self.chkHighQ, WIDGETS.W_ENABLE_HIGHQ)
[f721030]739
[f1f3e6a]740        self.mapper.addMapping(self.txtNptsLowQ, WIDGETS.W_NPTS_LOWQ)
741        self.mapper.addMapping(self.rbGuinier, WIDGETS.W_LOWQ_GUINIER)
742        self.mapper.addMapping(self.rbFitLowQ, WIDGETS.W_LOWQ_FIT)
743        self.mapper.addMapping(self.txtPowerLowQ, WIDGETS.W_LOWQ_POWER_VALUE)
[f721030]744
[f1f3e6a]745        self.mapper.addMapping(self.txtNptsHighQ, WIDGETS.W_NPTS_HIGHQ)
746        self.mapper.addMapping(self.rbFitHighQ, WIDGETS.W_HIGHQ_FIT)
747        self.mapper.addMapping(self.txtPowerHighQ, WIDGETS.W_HIGHQ_POWER_VALUE)
[f721030]748   
749        # Output
[f1f3e6a]750        self.mapper.addMapping(self.txtVolFract, WIDGETS.W_VOLUME_FRACTION)
751        self.mapper.addMapping(self.txtVolFractErr, WIDGETS.W_VOLUME_FRACTION_ERR)
752        self.mapper.addMapping(self.txtSpecSurf, WIDGETS.W_SPECIFIC_SURFACE)
753        self.mapper.addMapping(self.txtSpecSurfErr, WIDGETS.W_SPECIFIC_SURFACE_ERR)
754        self.mapper.addMapping(self.txtInvariantTot, WIDGETS.W_INVARIANT)
755        self.mapper.addMapping(self.txtInvariantTotErr, WIDGETS.W_INVARIANT_ERR)
[f721030]756
[f1f3e6a]757        self.mapper.toFirst()
[f721030]758
[f1f3e6a]759    def setData(self, data_item=None, is_batch=False):
[a281ab8]760        """
761        Obtain a QStandardItem object and dissect it to get Data1D/2D
762        Pass it over to the calculator
763        """
[f1f3e6a]764        assert data_item is not None
765
766        if self.txtName.text() == data_item[0].text():
767            logging.info('This file is already loaded in Invariant panel.')
768            return
769
[a281ab8]770        if not isinstance(data_item, list):
771            msg = "Incorrect type passed to the Invariant Perspective"
[b3e8629]772            raise AttributeError(msg)
[a281ab8]773
774        if not isinstance(data_item[0], QtGui.QStandardItem):
775            msg = "Incorrect type passed to the Invariant Perspective"
[b3e8629]776            raise AttributeError(msg)
[a281ab8]777
[f1f3e6a]778        # only 1 file can be loaded
[a281ab8]779        self._model_item = data_item[0]
780
781        # Extract data on 1st child - this is the Data1D/2D component
[8548d739]782        data = GuiUtils.dataFromItem(self._model_item)
[7c487846]783        self.model.item(WIDGETS.W_FILENAME).setData(self._model_item.text())
[f1f3e6a]784        # update GUI and model with info from loaded data
785        self.updateGuiFromFile(data=data)
[a281ab8]786
[f1f3e6a]787    def updateGuiFromFile(self, data=None):
[f721030]788        """
[f1f3e6a]789        update display in GUI and plot
[f721030]790        """
[f1f3e6a]791        self._data = data
792
793        # plot loaded file
794        if not isinstance(self._data, Data1D):
[6ae7466]795            msg = "Invariant cannot be computed with 2D data."
796            raise ValueError(msg)
[f1f3e6a]797
798        try:
799            filename = data.filename
800        except:
[6ae7466]801            msg = 'No filename chosen.'
[f1f3e6a]802            raise ValueError(msg)
803        try:
804            qmin = min(self._data.x)
805            qmax = max(self._data.x)
806        except:
807            msg = "Unable to find q min/max of \n data named %s" % \
808                  data.filename
809            raise ValueError(msg)
810
811        # update model with input form files: filename, qmin, qmax
812        self.model.item(WIDGETS.W_FILENAME).setText(filename)
813        self.model.item(WIDGETS.W_QMIN).setText(str(qmin))
814        self.model.item(WIDGETS.W_QMAX).setText(str(qmax))
815        self._path = filename
816
817        # Calculate and add to GUI: volume fraction, invariant total,
818        # and specific surface if porod checked
819        self.calculateInvariant()
[f721030]820
[5032ea68]821    def allowBatch(self):
822        """
823        Tell the caller that we don't accept multiple data instances
824        """
825        return False
Note: See TracBrowser for help on using the repository browser.