source: sasview/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py @ 740c381

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

Fix to solve hanging Invariant Perspective SASVIEW-1232

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