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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since b14db78 was b14db78, checked in by piotr, 6 years ago

Fixed splash screen in release.
Fixed dQ data resolution for files with dQ.
SASVIEW-1200

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