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

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

Use dQ/|Q| for 2D resolution. Cherry picked from master #209

  • Property mode set to 100644
File size: 14.1 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        if self.kernel_model is not None:
118            # model already present - recalculate
119            model = self.kernel_model
120            self.updateKernelModel(model)
121
122    def updateKernelModel(self, kernel_model=None):
123        """
124        Update the model
125        """
126        self.kernel_model = kernel_model
127        self.cbSmearing.blockSignals(True)
128        self.cbSmearing.clear()
129        self.cbSmearing.addItem("None")
130        if self.data is None:
131            self.setElementsVisibility(False)
132            return
133        if self.kernel_model is None:
134            return
135        # Find out if data has dQ
136        (self.smear_type, self.dq_l, self.dq_r) = self.getSmearInfo()
137        index_to_show = 0
138        if self.smear_type is not None:
139            self.cbSmearing.addItem(SMEARING_QD)
140            index_to_show = 1
141        if isinstance(self.data, Data1D):
142            self.cbSmearing.addItems(SMEARING_1D)
143        else:
144            self.cbSmearing.addItems(SMEARING_2D)
145        self.cbSmearing.blockSignals(False)
146        self.cbSmearing.setCurrentIndex(index_to_show)
147
148    def smearer(self):
149        """ Returns the current smearer """
150        return self.current_smearer
151
152    def onIndexChange(self, index):
153        """
154        Callback for smearing combobox index change
155        """
156        text = self.cbSmearing.currentText()
157        if text == 'None':
158            self.setElementsVisibility(False)
159            self.current_smearer = None
160        elif text == "Use dQ Data":
161            self.setElementsVisibility(True)
162            self.setDQLabels()
163            if self.smear_type == "Pinhole":
164                self.onPinholeSmear()
165            else:
166                self.onSlitSmear()
167        elif text == "Custom Pinhole Smear":
168            self.setElementsVisibility(True)
169            self.setPinholeLabels()
170            self.onPinholeSmear()
171        elif text == "Custom Slit Smear":
172            self.setElementsVisibility(True)
173            self.setSlitLabels()
174            self.onSlitSmear()
175        self.smearingChangedSignal.emit()
176
177    def onModelChange(self):
178        """
179        Respond to model change by notifying any listeners
180        """
181        # Recalculate the smearing
182        index = self.cbSmearing.currentIndex()
183        self.onIndexChange(index)
184
185    def setElementsVisibility(self, visible):
186        """
187        Labels and linedits visibility control
188        """
189        self.lblSmearDown.setVisible(visible)
190        self.lblSmearUp.setVisible(visible)
191        self.txtSmearDown.setVisible(visible)
192        self.txtSmearUp.setVisible(visible)
193        self.lblUnitUp.setVisible(visible)
194        self.lblUnitDown.setVisible(visible)
195        self.setAccuracyVisibility()
196
197    def setAccuracyVisibility(self):
198        """
199        Accuracy combobox visibility
200        """
201        if isinstance(self.data, Data2D) and self.cbSmearing.currentIndex() == 1:
202            self.gAccuracy.setVisible(True)
203        else:
204            self.gAccuracy.setVisible(False)
205
206    def setPinholeLabels(self):
207        """
208        Use pinhole labels
209        """
210        self.txtSmearDown.setVisible(False)
211        self.lblSmearDown.setText('')
212        self.lblUnitDown.setText('')
213        if isinstance(self.data, Data2D):
214            self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
215            self.lblSmearUp.setText('<html><head/><body><p>&lt;dQ<span style=" vertical-align:sub;">low</span>&gt;</p></body></html>')
216        else:
217            self.lblSmearUp.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">%</span></p></body></html>')
218            self.lblUnitUp.setText('%')
219        self.txtSmearDown.setEnabled(True)
220        self.txtSmearUp.setEnabled(True)
221        #self.txtSmearDown.setText(str(0.0))
222        #self.txtSmearUp.setText(str(0.0))
223
224    def setSlitLabels(self):
225        """
226        Use pinhole labels
227        """
228        self.lblSmearUp.setText('Slit height')
229        self.lblSmearDown.setText('Slit width')
230        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
231        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
232        self.txtSmearDown.setEnabled(True)
233        self.txtSmearUp.setEnabled(True)
234        #self.txtSmearDown.setText(str(0.0))
235        #self.txtSmearUp.setText(str(0.0))
236
237    def setDQLabels(self):
238        """
239        Use pinhole labels
240        """
241        if self.smear_type == "Pinhole":
242            self.lblSmearDown.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">high</span></p></body></html>')
243            self.lblSmearUp.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">low</span></p></body></html>')
244        else:
245            self.lblSmearUp.setText('<dQp>')
246            self.lblSmearDown.setText('<dQs>')
247        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
248        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
249        self.txtSmearDown.setText(str(self.dq_r))
250        self.txtSmearUp.setText(str(self.dq_l))
251        self.txtSmearDown.setEnabled(False)
252        self.txtSmearUp.setEnabled(False)
253
254    def state(self):
255        """
256        Returns current state of controls
257        """
258        smearing = self.cbSmearing.currentText()
259        accuracy = ""
260        d_down = None
261        d_up = None
262        if smearing != "None":
263            accuracy = str(self.model.item(MODEL.index('ACCURACY')).text())
264            try:
265                d_down = float(self.model.item(MODEL.index('PINHOLE_MIN')).text())
266            except ValueError:
267                d_down = None
268            try:
269                d_up = float(self.model.item(MODEL.index('PINHOLE_MAX')).text())
270            except ValueError:
271                d_up = None
272
273        return (smearing, accuracy, d_down, d_up)
274
275    def setState(self, smearing, accuracy, d_down, d_up):
276        """
277        Sets new values for the controls
278        """
279        # Update the model -> controls update automatically
280        #if smearing is not None:
281            #self.model.item(MODEL.index('SMEARING')).setText(smearing)
282        if accuracy is not None:
283            self.model.item(MODEL.index('ACCURACY')).setText(accuracy)
284        if d_down is not None:
285            self.model.item(MODEL.index('PINHOLE_MIN')).setText(d_down)
286        if d_up is not None:
287            self.model.item(MODEL.index('PINHOLE_MAX')).setText(d_up)
288
289    def onPinholeSmear(self):
290        """
291        Create a custom pinhole smear object that will change the way residuals
292        are compute when fitting
293        """
294        _, accuracy, d_percent, _ = self.state()
295        if d_percent is None or d_percent == 0.0:
296            self.current_smearer=None
297            return
298        percent = d_percent/100.0
299        # copy data
300        data = copy.deepcopy(self.data)
301        if isinstance(self.data, Data2D):
302            len_data = len(data.data)
303            data.dqx_data = np.zeros(len_data)
304            data.dqy_data = np.zeros(len_data)
305            data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
306            data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
307            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
308            data.dx_data = data.dqy_data = percent*q
309        else:
310            len_data = len(data.x)
311            data.dx = np.zeros(len_data)
312            data.dx = percent * data.x
313            data.dxl = None
314            data.dxw = None
315
316        self.current_smearer = smear_selection(data, self.kernel_model)
317        # need to set accuracy for 2D
318        if isinstance(self.data, Data2D):
319            backend_accuracy = ACCURACY_DICT.get(accuracy)
320            if backend_accuracy:
321                self.current_smearer.set_accuracy(accuracy=backend_accuracy)
322
323    def onSlitSmear(self):
324        """
325        Create a custom slit smear object that will change the way residuals
326        are compute when fitting
327        """
328        _, accuracy, d_height, d_width = self.state()
329
330        # Check changes in slit width
331        if d_width is None:
332            d_width = 0.0
333        if d_height is None:
334            d_height = 0.0
335
336        if isinstance(self.data, Data2D):
337            self.current_smearer = smear_selection(self.data, self.kernel_model)
338            return
339        # make sure once more if it is smearer
340        data = copy.deepcopy(self.data)
341        data_len = len(data.x)
342        data.dx = None
343        data.dxl = None
344        data.dxw = None
345
346        try:
347            self.dxl = d_height
348            data.dxl = self.dxl * np.ones(data_len)
349        except:
350            self.dxl = None
351            data.dxl = np.zeros(data_len)
352        try:
353            self.dxw = d_width
354            data.dxw = self.dxw * np.ones(data_len)
355        except:
356            self.dxw = None
357            data.dxw = np.zeros(data_len)
358
359        self.current_smearer = smear_selection(data, self.kernel_model)
360
361    def getSmearInfo(self):
362        """
363        Get the smear info from data.
364
365        :return: self.smear_type, self.dq_l and self.dq_r,
366            respectively the type of the smear, dq_min and
367            dq_max for pinhole smear data
368            while dxl and dxw for slit smear
369        """
370        # default
371        smear_type = None
372        dq_l = None
373        dq_r = None
374        data = self.data
375        if self.data is None:
376            return smear_type, dq_l, dq_r
377        elif isinstance(data, Data2D):
378            if data.dqx_data is None or data.dqy_data is None:
379                return smear_type, dq_l, dq_r
380            elif data.dqx_data.any() != 0 and data.dqx_data.any() != 0:
381                smear_type = "Pinhole2d"
382                dq_l = GuiUtils.formatNumber(np.average(data.dqx_data))
383                dq_r = GuiUtils.formatNumber(np.average(data.dqy_data))
384                return smear_type, dq_l, dq_r
385            else:
386                return smear_type, dq_l, dq_r
387        # check if it is pinhole smear and get min max if it is.
388        if data.dx is not None and np.any(data.dx):
389            smear_type = "Pinhole"
390            dq_l = data.dx[0]
391            dq_r = data.dx[-1]
392
393        # check if it is slit smear and get min max if it is.
394        elif data.dxl is not None or data.dxw is not None:
395            smear_type = "Slit"
396            if data.dxl is not None and np.all(data.dxl, 0):
397                dq_l = data.dxl[0]
398            if data.dxw is not None and np.all(data.dxw, 0):
399                dq_r = data.dxw[0]
400
401        return smear_type, dq_l, dq_r
Note: See TracBrowser for help on using the repository browser.