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

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

Disabling reporting for perspectives other than fitting

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