source: sasview/src/sas/qtgui/Perspectives/Fitting/SmearingWidget.py @ bad4a52

ESS_GUI_iss1116
Last change on this file since bad4a52 was a5f7bf4, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Fixed parentheses placement + typo

  • Property mode set to 100644
File size: 15.0 KB
Line 
1"""
2Widget/logic for smearing data.
3"""
4import copy
5import numpy as np
6from PyQt5 import QtCore
7from PyQt5 import QtGui
8from PyQt5 import QtWidgets
9
10from sas.sascalc.fit.qsmearing import smear_selection
11from sas.qtgui.Plotting.PlotterData import Data1D
12from sas.qtgui.Plotting.PlotterData import Data2D
13import sas.qtgui.Utilities.GuiUtils as GuiUtils
14
15# Local UI
16from sas.qtgui.Perspectives.Fitting.UI.SmearingWidgetUI import Ui_SmearingWidgetUI
17
18class DataWidgetMapper(QtWidgets.QDataWidgetMapper):
19    """
20    Custom version of the standard QDataWidgetMapper allowing for proper
21    response to index change in comboboxes
22    """
23    def addMapping(self, widget, section, propertyName=None):
24        if propertyName is None:
25            super(DataWidgetMapper, self).addMapping(widget, section)
26        else:
27            super(DataWidgetMapper, self).addMapping(widget, section, propertyName)
28
29        if isinstance(widget, QtWidgets.QComboBox):
30            delegate = self.itemDelegate()
31            widget.currentIndexChanged.connect(lambda: delegate.commitData.emit(widget))
32
33SMEARING_1D = ["Custom Pinhole Smear", "Custom Slit Smear"]
34SMEARING_2D = ["Custom Pinhole Smear"]
35SMEARING_QD = "Use dQ Data"
36
37MODEL = [
38    'SMEARING',
39    'PINHOLE_MIN',
40    'PINHOLE_MAX',
41    'ACCURACY']
42ACCURACY_DICT={'Low': 'low',
43               'Medium': 'med',
44               'High': 'high',
45               'Extra high': 'xhigh'}
46
47DEFAULT_PINHOLE_UP=0.0
48DEFAULT_PINHOLE_DOWN=0.0
49
50class SmearingWidget(QtWidgets.QWidget, Ui_SmearingWidgetUI):
51    smearingChangedSignal = QtCore.pyqtSignal()
52    def __init__(self, parent=None):
53        super(SmearingWidget, self).__init__()
54
55        self.setupUi(self)
56
57        # Local model for holding data
58        self.model = None
59        # Mapper for model update
60        self.mapper = None
61        # Data from the widget
62        self.data = None
63        self.current_smearer = None
64        self.kernel_model = None
65        # dQ data variables
66        smear_type = None
67        dq_l = None
68        dq_r = None
69        # Let only floats in the line edits
70        self.txtSmearDown.setValidator(GuiUtils.DoubleValidator())
71        self.txtSmearUp.setValidator(GuiUtils.DoubleValidator())
72
73        # Attach slots
74        self.cbSmearing.currentIndexChanged.connect(self.onIndexChange)
75        self.cbSmearing.setCurrentIndex(0)
76        self.txtSmearUp.setText(str(DEFAULT_PINHOLE_UP))
77        self.txtSmearDown.setText(str(DEFAULT_PINHOLE_DOWN))
78
79        self.initModel()
80        self.initMapper()
81
82    def initModel(self):
83        """
84        Initialize the state
85        """
86        self.model = QtGui.QStandardItemModel()
87        for model_item in range(len(MODEL)):
88            self.model.setItem(model_item, QtGui.QStandardItem())
89        # Attach slot
90        self.model.dataChanged.connect(self.onModelChange)
91
92    def initMapper(self):
93        """
94        Initialize model item <-> UI element mapping
95        """
96        self.mapper = DataWidgetMapper(self)
97
98        self.mapper.setModel(self.model)
99        self.mapper.setOrientation(QtCore.Qt.Vertical)
100
101        self.mapper.addMapping(self.txtSmearUp,   MODEL.index('PINHOLE_MIN'))
102        self.mapper.addMapping(self.txtSmearDown, MODEL.index('PINHOLE_MAX'))
103        self.mapper.addMapping(self.cbAccuracy,   MODEL.index('ACCURACY'))
104
105        self.mapper.toFirst()
106
107    def updateData(self, data=None):
108        """
109        Update control elements based on data and model passed
110        """
111        self.cbSmearing.clear()
112        self.cbSmearing.addItem("None")
113        self.gAccuracy.setVisible(False)
114        self.data = data
115        if data is None:
116            self.setElementsVisibility(False)
117        model = self.kernel_model
118        self.updateKernelModel(model)
119
120    def updateKernelModel(self, kernel_model=None):
121        """
122        Update the model
123        """
124        self.kernel_model = kernel_model
125        self.cbSmearing.blockSignals(True)
126        self.cbSmearing.clear()
127        self.cbSmearing.addItem("None")
128        if self.data is None:
129            self.setElementsVisibility(False)
130            return
131        # Find out if data has dQ
132        (self.smear_type, self.dq_l, self.dq_r) = self.getSmearInfo()
133        index_to_show = 0
134        if self.smear_type is not None:
135            self.cbSmearing.addItem(SMEARING_QD)
136            index_to_show = 1
137
138        if self.kernel_model is None:
139            # No model definend yet - just use data file smearing, if any
140            self.cbSmearing.blockSignals(False)
141            self.cbSmearing.setCurrentIndex(index_to_show)
142            return
143
144        if isinstance(self.data, Data1D):
145            self.cbSmearing.addItems(SMEARING_1D)
146        else:
147            self.cbSmearing.addItems(SMEARING_2D)
148        self.cbSmearing.blockSignals(False)
149        self.cbSmearing.setCurrentIndex(index_to_show)
150
151    def smearer(self):
152        """ Returns the current smearer """
153        return self.current_smearer
154
155    def onIndexChange(self, index):
156        """
157        Callback for smearing combobox index change
158        """
159        text = self.cbSmearing.currentText()
160        if text == 'None':
161            self.setElementsVisibility(False)
162            self.current_smearer = None
163        elif text == "Use dQ Data":
164            self.setElementsVisibility(True)
165            self.setDQLabels()
166            if self.smear_type == "Pinhole":
167                self.onPinholeSmear()
168            else:
169                self.onSlitSmear()
170        elif text == "Custom Pinhole Smear":
171            self.setElementsVisibility(True)
172            self.setPinholeLabels()
173            self.onPinholeSmear()
174        elif text == "Custom Slit Smear":
175            self.setElementsVisibility(True)
176            self.setSlitLabels()
177            self.onSlitSmear()
178        self.smearingChangedSignal.emit()
179
180    def onModelChange(self):
181        """
182        Respond to model change by notifying any listeners
183        """
184        # Recalculate the smearing
185        index = self.cbSmearing.currentIndex()
186        self.onIndexChange(index)
187
188    def setElementsVisibility(self, visible):
189        """
190        Labels and linedits visibility control
191        """
192        self.lblSmearDown.setVisible(visible)
193        self.lblSmearUp.setVisible(visible)
194        self.txtSmearDown.setVisible(visible)
195        self.txtSmearUp.setVisible(visible)
196        self.lblUnitUp.setVisible(visible)
197        self.lblUnitDown.setVisible(visible)
198        self.setAccuracyVisibility()
199
200    def setAccuracyVisibility(self):
201        """
202        Accuracy combobox visibility
203        """
204        if isinstance(self.data, Data2D) and self.cbSmearing.currentIndex() == 1:
205            self.gAccuracy.setVisible(True)
206        else:
207            self.gAccuracy.setVisible(False)
208
209    def setPinholeLabels(self):
210        """
211        Use pinhole labels
212        """
213        self.txtSmearDown.setVisible(False)
214        self.lblSmearDown.setText('')
215        self.lblUnitDown.setText('')
216        if isinstance(self.data, Data2D):
217            self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
218            self.lblSmearUp.setText('<html><head/><body><p>&lt;dQ<span style=" vertical-align:sub;">low</span>&gt;</p></body></html>')
219        else:
220            self.lblSmearUp.setText('<html><head/><body><p>dQ/Q</p></body></html>')
221            self.lblUnitUp.setText('%')
222        self.txtSmearDown.setEnabled(True)
223        self.txtSmearUp.setEnabled(True)
224
225    def setSlitLabels(self):
226        """
227        Use pinhole labels
228        """
229        self.lblSmearUp.setText('Slit height')
230        self.lblSmearDown.setText('Slit width')
231        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
232        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
233        self.txtSmearDown.setEnabled(True)
234        self.txtSmearUp.setEnabled(True)
235
236    def setDQLabels(self):
237        """
238        Use appropriate labels
239        """
240        if self.smear_type == "Pinhole":
241            text_down = '<html><head/><body><p>[dQ/Q]<span style=" vertical-align:sub;">max</span></p></body></html>'
242            text_up = '<html><head/><body><p>[dQ/Q]<span style=" vertical-align:sub;">min</span></p></body></html>'
243            text_unit = '%'
244        elif self.smear_type == "Slit":
245            text_down = '<html><head/><body><p>Slit width</p></body></html>'
246            text_up = '<html><head/><body><p>Slit height</p></body></html>'
247            text_unit = '<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>'
248        else:
249            text_unit = '%'
250            text_up = '<html><head/><body><p>&lsaquo;dQ/Q&rsaquo;<span style=" vertical-align:sub;">r</span></p></body></html>'
251            text_down = '<html><head/><body><p>&lsaquo;dQ/Q&rsaquo;<span style=" vertical-align:sub;">&phi;</span></p></body></html>'
252
253        self.lblSmearDown.setText(text_down)
254        self.lblSmearUp.setText(text_up)
255
256        self.lblUnitUp.setText(text_unit)
257        self.lblUnitDown.setText(text_unit)
258
259        self.txtSmearDown.setText(str(self.dq_r))
260        self.txtSmearUp.setText(str(self.dq_l))
261        self.txtSmearDown.setEnabled(False)
262        self.txtSmearUp.setEnabled(False)
263
264    def state(self):
265        """
266        Returns current state of controls
267        """
268        smearing = self.cbSmearing.currentText()
269        accuracy = ""
270        d_down = None
271        d_up = None
272        if smearing != "None":
273            accuracy = str(self.model.item(MODEL.index('ACCURACY')).text())
274            try:
275                d_down = float(self.model.item(MODEL.index('PINHOLE_MIN')).text())
276            except ValueError:
277                d_down = None
278            try:
279                d_up = float(self.model.item(MODEL.index('PINHOLE_MAX')).text())
280            except ValueError:
281                d_up = None
282
283        return (smearing, accuracy, d_down, d_up)
284
285    def setState(self, smearing, accuracy, d_down, d_up):
286        """
287        Sets new values for the controls
288        """
289        # Update the model -> controls update automatically
290        #if smearing is not None:
291            #self.model.item(MODEL.index('SMEARING')).setText(smearing)
292        if accuracy is not None:
293            self.model.item(MODEL.index('ACCURACY')).setText(accuracy)
294        if d_down is not None:
295            self.model.item(MODEL.index('PINHOLE_MIN')).setText(d_down)
296        if d_up is not None:
297            self.model.item(MODEL.index('PINHOLE_MAX')).setText(d_up)
298
299    def onPinholeSmear(self):
300        """
301        Create a custom pinhole smear object that will change the way residuals
302        are compute when fitting
303        """
304        _, accuracy, d_percent, _ = self.state()
305        if d_percent is None or d_percent == 0.0:
306            self.current_smearer=None
307            return
308        percent = d_percent/100.0
309        # copy data
310        data = copy.deepcopy(self.data)
311        if isinstance(self.data, Data2D):
312            len_data = len(data.data)
313            data.dqx_data = np.zeros(len_data)
314            data.dqy_data = np.zeros(len_data)
315            data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
316            data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
317            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
318            data.dx_data = data.dqy_data = percent*q
319        else:
320            len_data = len(data.x)
321            data.dx = np.zeros(len_data)
322            data.dx = percent * data.x
323            data.dxl = None
324            data.dxw = None
325
326        self.current_smearer = smear_selection(data, self.kernel_model)
327        # need to set accuracy for 2D
328        if isinstance(self.data, Data2D):
329            backend_accuracy = ACCURACY_DICT.get(accuracy)
330            if backend_accuracy:
331                self.current_smearer.set_accuracy(accuracy=backend_accuracy)
332
333    def onSlitSmear(self):
334        """
335        Create a custom slit smear object that will change the way residuals
336        are compute when fitting
337        """
338        _, accuracy, d_height, d_width = self.state()
339
340        # Check changes in slit width
341        if d_width is None:
342            d_width = 0.0
343        if d_height is None:
344            d_height = 0.0
345
346        if isinstance(self.data, Data2D):
347            self.current_smearer = smear_selection(self.data, self.kernel_model)
348            return
349        # make sure once more if it is smearer
350        data = copy.deepcopy(self.data)
351        data_len = len(data.x)
352        data.dx = None
353        data.dxl = None
354        data.dxw = None
355
356        try:
357            self.dxl = d_height
358            data.dxl = self.dxl * np.ones(data_len)
359        except:
360            self.dxl = None
361            data.dxl = np.zeros(data_len)
362        try:
363            self.dxw = d_width
364            data.dxw = self.dxw * np.ones(data_len)
365        except:
366            self.dxw = None
367            data.dxw = np.zeros(data_len)
368
369        self.current_smearer = smear_selection(data, self.kernel_model)
370
371    def getSmearInfo(self):
372        """
373        Get the smear info from data.
374
375        :return: self.smear_type, self.dq_l and self.dq_r,
376            respectively the type of the smear, the average <dq/q> radial(p)
377            and <dq/q> theta (s)s for 2D pinhole resolution in % (slit is not
378            currently supported in 2D), (dq/q)_min and (dq/q)_max for 1D pinhole
379            smeared data, again in %, and dxl and/or dxw for slit smeared data
380            given in 1/A and assumed constant.
381        """
382        # default
383        smear_type = None
384        dq_l = None
385        dq_r = None
386        data = self.data
387        if self.data is None:
388            return smear_type, dq_l, dq_r
389        # First check if data is 2D
390        # If so check that data set has smearing info and that none are zero.
391        # Otherwise no smearing can be applied using smear from data (a Gaussian
392        # width of zero will cause a divide by zero error)
393        elif isinstance(data, Data2D):
394            if data.dqx_data is None or data.dqy_data is None:
395                return smear_type, dq_l, dq_r
396            elif data.dqx_data.any() != 0 and data.dqy_data.any() != 0:
397                smear_type = "Pinhole2d"
398                dq_l = GuiUtils.formatNumber(np.average(data.dqx_data/np.abs(data.qx_data))*100., high=True)
399                dq_r = GuiUtils.formatNumber(np.average(data.dqy_data/np.abs(data.qy_data))*100., high=True)
400                return smear_type, dq_l, dq_r
401            else:
402                return smear_type, dq_l, dq_r
403        # check if it is pinhole smear and get min max if it is.
404        if data.dx is not None and np.all(data.dx):
405            smear_type = "Pinhole"
406            dq_l = GuiUtils.formatNumber(data.dx[0]/data.x[0] *100., high=True)
407            dq_r = GuiUtils.formatNumber(data.dx[-1]/data.x[-1] *100., high=True)
408
409        # check if it is slit smear and get min max if it is.
410        elif data.dxl is not None or data.dxw is not None:
411            smear_type = "Slit"
412            if data.dxl is not None and np.all(data.dxl, 0):
413                dq_l = GuiUtils.formatNumber(data.dxl[0])
414            if data.dxw is not None and np.all(data.dxw, 0):
415                dq_r = GuiUtils.formatNumber(data.dxw[0])
416
417        return smear_type, dq_l, dq_r
Note: See TracBrowser for help on using the repository browser.