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

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

Decoupling low and high q errors

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