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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 8b6e4be was 8b6e4be, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 months ago

check data and kernel model before using them. SASVIEW-1162

  • Property mode set to 100644
File size: 10.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"]
35
36MODEL = [
37    'SMEARING',
38    'PINHOLE_MIN',
39    'PINHOLE_MAX',
40    'ACCURACY']
41ACCURACY_DICT={'Low': 'low',
42               'Medium': 'med',
43               'High': 'high',
44               'Extra high': 'xhigh'}
45
46DEFAULT_PINHOLE_UP=0.0
47DEFAULT_PINHOLE_DOWN=0.0
48
49class SmearingWidget(QtWidgets.QWidget, Ui_SmearingWidgetUI):
50    smearingChangedSignal = QtCore.pyqtSignal()
51    def __init__(self, parent=None):
52        super(SmearingWidget, self).__init__()
53
54        self.setupUi(self)
55
56        # Local model for holding data
57        self.model = None
58        # Mapper for model update
59        self.mapper = None
60        # Data from the widget
61        self.data = None
62        self.current_smearer = None
63        self.kernel_model = None
64
65        # Let only floats in the line edits
66        self.txtSmearDown.setValidator(GuiUtils.DoubleValidator())
67        self.txtSmearUp.setValidator(GuiUtils.DoubleValidator())
68
69        # Attach slots
70        self.cbSmearing.currentIndexChanged.connect(self.onIndexChange)
71        self.cbSmearing.setCurrentIndex(0)
72        self.txtSmearUp.setText(str(DEFAULT_PINHOLE_UP))
73        self.txtSmearDown.setText(str(DEFAULT_PINHOLE_DOWN))
74
75        self.initModel()
76        self.initMapper()
77
78    def initModel(self):
79        """
80        Initialize the state
81        """
82        self.model = QtGui.QStandardItemModel()
83        for model_item in range(len(MODEL)):
84            self.model.setItem(model_item, QtGui.QStandardItem())
85        # Attach slot
86        self.model.dataChanged.connect(self.onModelChange)
87
88    def initMapper(self):
89        """
90        Initialize model item <-> UI element mapping
91        """
92        self.mapper = DataWidgetMapper(self)
93
94        self.mapper.setModel(self.model)
95        self.mapper.setOrientation(QtCore.Qt.Vertical)
96
97        self.mapper.addMapping(self.txtSmearUp,   MODEL.index('PINHOLE_MIN'))
98        self.mapper.addMapping(self.txtSmearDown, MODEL.index('PINHOLE_MAX'))
99        self.mapper.addMapping(self.cbAccuracy,   MODEL.index('ACCURACY'))
100
101        self.mapper.toFirst()
102
103    def updateData(self, data=None):
104        """
105        Update control elements based on data and model passed
106        """
107        self.cbSmearing.clear()
108        self.cbSmearing.addItem("None")
109        self.gAccuracy.setVisible(False)
110        self.data = data
111        if data is None:
112            self.setElementsVisibility(False)
113
114    def updateKernelModel(self, kernel_model=None):
115        """
116        Update the model
117        """
118        self.kernel_model = kernel_model
119        if self.data is None:
120            self.setElementsVisibility(False)
121            return
122        if self.kernel_model is None:
123            return
124        elif isinstance(self.data, Data1D):
125            self.cbSmearing.addItems(SMEARING_1D)
126        else:
127            self.cbSmearing.addItems(SMEARING_2D)
128        self.cbSmearing.setCurrentIndex(0)
129
130    def smearer(self):
131        """ Returns the current smearer """
132        return self.current_smearer
133
134    def onIndexChange(self, index):
135        """
136        Callback for smearing combobox index change
137        """
138        if index == 0:
139            self.setElementsVisibility(False)
140            self.current_smearer = None
141        elif index == 1:
142            self.setElementsVisibility(True)
143            self.setPinholeLabels()
144            self.onPinholeSmear()
145        elif index == 2:
146            self.setElementsVisibility(True)
147            self.setSlitLabels()
148            self.onSlitSmear()
149        self.smearingChangedSignal.emit()
150
151    def onModelChange(self):
152        """
153        Respond to model change by notifying any listeners
154        """
155        # Recalculate the smearing
156        index = self.cbSmearing.currentIndex()
157        self.onIndexChange(index)
158
159    def setElementsVisibility(self, visible):
160        """
161        Labels and linedits visibility control
162        """
163        self.lblSmearDown.setVisible(visible)
164        self.lblSmearUp.setVisible(visible)
165        self.txtSmearDown.setVisible(visible)
166        self.txtSmearUp.setVisible(visible)
167        self.lblUnitUp.setVisible(visible)
168        self.lblUnitDown.setVisible(visible)
169        self.setAccuracyVisibility()
170
171    def setAccuracyVisibility(self):
172        """
173        Accuracy combobox visibility
174        """
175        if isinstance(self.data, Data2D) and self.cbSmearing.currentIndex() == 1:
176            self.gAccuracy.setVisible(True)
177        else:
178            self.gAccuracy.setVisible(False)
179
180    def setPinholeLabels(self):
181        """
182        Use pinhole labels
183        """
184        self.txtSmearDown.setVisible(False)
185        self.lblSmearDown.setText('')
186        self.lblUnitDown.setText('')
187        if isinstance(self.data, Data2D):
188            self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
189            self.lblSmearUp.setText('<html><head/><body><p>&lt;dQ<span style=" vertical-align:sub;">low</span>&gt;</p></body></html>')
190        else:
191            self.lblSmearUp.setText('<html><head/><body><p>dQ<span style=" vertical-align:sub;">%</span></p></body></html>')
192            self.lblUnitUp.setText('%')
193
194    def setSlitLabels(self):
195        """
196        Use pinhole labels
197        """
198        self.lblSmearUp.setText('Slit height')
199        self.lblSmearDown.setText('Slit width')
200        self.lblUnitUp.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
201        self.lblUnitDown.setText('<html><head/><body><p>Å<span style=" vertical-align:super;">-1</span></p></body></html>')
202
203    def state(self):
204        """
205        Returns current state of controls
206        """
207        smearing = self.cbSmearing.currentText()
208        accuracy = ""
209        d_down = None
210        d_up = None
211        if smearing != "None":
212            accuracy = str(self.model.item(MODEL.index('ACCURACY')).text())
213            try:
214                d_down = float(self.model.item(MODEL.index('PINHOLE_MIN')).text())
215            except ValueError:
216                d_down = None
217            try:
218                d_up = float(self.model.item(MODEL.index('PINHOLE_MAX')).text())
219            except ValueError:
220                d_up = None
221
222        return (smearing, accuracy, d_down, d_up)
223
224    def setState(self, smearing, accuracy, d_down, d_up):
225        """
226        Sets new values for the controls
227        """
228        # Update the model -> controls update automatically
229        #if smearing is not None:
230            #self.model.item(MODEL.index('SMEARING')).setText(smearing)
231        if accuracy is not None:
232            self.model.item(MODEL.index('ACCURACY')).setText(accuracy)
233        if d_down is not None:
234            self.model.item(MODEL.index('PINHOLE_MIN')).setText(d_down)
235        if d_up is not None:
236            self.model.item(MODEL.index('PINHOLE_MAX')).setText(d_up)
237
238    def onPinholeSmear(self):
239        """
240        Create a custom pinhole smear object that will change the way residuals
241        are compute when fitting
242        """
243        _, accuracy, d_percent, _ = self.state()
244        if d_percent is None or d_percent == 0.0:
245            self.current_smearer=None
246            return
247        percent = d_percent/100.0
248        # copy data
249        data = copy.deepcopy(self.data)
250        if isinstance(self.data, Data2D):
251            len_data = len(data.data)
252            data.dqx_data = np.zeros(len_data)
253            data.dqy_data = np.zeros(len_data)
254            data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
255            data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
256        else:
257            len_data = len(data.x)
258            data.dx = np.zeros(len_data)
259            data.dx = percent * data.x
260            data.dxl = None
261            data.dxw = None
262
263        self.current_smearer = smear_selection(data, self.kernel_model)
264        # need to set accuracy for 2D
265        if isinstance(self.data, Data2D):
266            backend_accuracy = ACCURACY_DICT.get(accuracy)
267            if backend_accuracy:
268                self.current_smearer.set_accuracy(accuracy=backend_accuracy)
269
270    def onSlitSmear(self):
271        """
272        Create a custom slit smear object that will change the way residuals
273        are compute when fitting
274        """
275        _, accuracy, d_height, d_width = self.state()
276        # Check changes in slit width
277        if d_width is None:
278            d_width = 0.0
279        if d_height is None:
280            d_height = 0.0
281
282        if isinstance(self.data, Data2D):
283            return
284        # make sure once more if it is smearer
285        data = copy.deepcopy(self.data)
286        data_len = len(data.x)
287        data.dx = None
288        data.dxl = None
289        data.dxw = None
290
291        try:
292            self.dxl = d_height
293            data.dxl = self.dxl * np.ones(data_len)
294        except:
295            self.dxl = None
296            data.dxl = np.zeros(data_len)
297        try:
298            self.dxw = d_width
299            data.dxw = self.dxw * np.ones(data_len)
300        except:
301            self.dxw = None
302            data.dxw = np.zeros(data_len)
303
304        self.current_smearer = smear_selection(data, self.kernel_model)
Note: See TracBrowser for help on using the repository browser.