source: sasview/src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py @ 728b291

Last change on this file since 728b291 was 505357a, checked in by Adam Washington <adam.washington@…>, 7 years ago

Better threadsafety through signals

  • Property mode set to 100644
File size: 11.7 KB
Line 
1"""
2This module provides the intelligence behind the gui interface for Corfunc.
3"""
4# pylint: disable=E1101
5
6# global
7from PyQt4 import QtCore
8from PyQt4 import QtGui
9
10# sas-global
11# pylint: disable=import-error, no-name-in-module
12import sas.qtgui.Utilities.GuiUtils as GuiUtils
13from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
14# pylint: enable=import-error, no-name-in-module
15
16# local
17from UI.CorfuncPanel import Ui_CorfuncDialog
18# from InvariantDetails import DetailsDialog
19from CorfuncUtils import WIDGETS as W
20
21from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
22    as FigureCanvas
23from matplotlib.figure import Figure
24
25
26class MyMplCanvas(FigureCanvas):
27    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
28    def __init__(self, model, width=5, height=4, dpi=100):
29        self.model = model
30        self.fig = Figure(figsize=(width, height), dpi=dpi)
31        self.axes = self.fig.add_subplot(111)
32
33        FigureCanvas.__init__(self, self.fig)
34
35        self.data = None
36        self.extrap = None
37
38    def draw_q_space(self):
39        """Draw the Q space data in the plot window
40
41        This draws the q space data in self.data, as well
42        as the bounds set by self.qmin, self.qmax1, and self.qmax2.
43        It will also plot the extrpolation in self.extrap, if it exists."""
44
45        self.fig.clf()
46
47        self.axes = self.fig.add_subplot(111)
48        self.axes.set_xscale("log")
49        self.axes.set_yscale("log")
50
51        qmin = float(self.model.item(W.W_QMIN).text())
52        qmax1 = float(self.model.item(W.W_QMAX).text())
53        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
54
55        if self.data:
56            self.axes.plot(self.data.x, self.data.y)
57            self.axes.axvline(qmin)
58            self.axes.axvline(qmax1)
59            self.axes.axvline(qmax2)
60            self.axes.set_xlim(min(self.data.x) / 2,
61                               max(self.data.x) * 1.5 - 0.5 * min(self.data.x))
62        if self.extrap:
63            self.axes.plot(self.extrap.x, self.extrap.y)
64
65        self.draw()
66
67    def draw_real_space(self):
68        """
69        This function draws the real space data onto the plot
70
71        The 1d correlation function in self.data, the 3d correlation function
72        in self.data3, and the interface distribution function in self.data_idf
73        are all draw in on the plot in linear cooredinates."""
74        self.fig.clf()
75
76        self.axes = self.fig.add_subplot(111)
77        self.axes.set_xscale("linear")
78        self.axes.set_yscale("linear")
79
80        if self.data:
81            data1, data3, data_idf = self.data
82            self.axes.plot(data1.x, data1.y, label="1D Correlation")
83            self.axes.plot(data3.x, data3.y, label="3D Correlation")
84            self.axes.plot(data_idf.x, data_idf.y,
85                           label="Interface Distribution Function")
86            self.axes.set_xlim(min(data1.x), max(data1.x) / 4)
87            self.axes.legend()
88
89        self.draw()
90
91
92class CorfuncWindow(QtGui.QDialog, Ui_CorfuncDialog):
93    """Displays the correlation function analysis of sas data."""
94    name = "Corfunc"  # For displaying in the combo box
95
96    trigger = QtCore.pyqtSignal(QtCore.QVariant)
97
98# pylint: disable=unused-argument
99    def __init__(self, parent=None):
100        super(CorfuncWindow, self).__init__()
101        self.setupUi(self)
102
103        self.setWindowTitle("Corfunc Perspective")
104
105        self.mapper = None
106        self.model = QtGui.QStandardItemModel(self)
107        self.communicate = GuiUtils.Communicate()
108        self._calculator = CorfuncCalculator()
109        self._allow_close = True
110
111        self._canvas = MyMplCanvas(self.model)
112        self._realplot = MyMplCanvas(self.model)
113        self.verticalLayout_7.insertWidget(0, self._canvas)
114        self.verticalLayout_7.insertWidget(1, self._realplot)
115
116        # Connect buttons to slots.
117        # Needs to be done early so default values propagate properly.
118        self.setup_slots()
119
120        # Set up the model.
121        self.setup_model()
122
123        # Set up the mapper
124        self.setup_mapper()
125
126    def setup_slots(self):
127        """Connect the buttons to their appropriate slots."""
128        self.extrapolateBtn.clicked.connect(self.extrapolate)
129        self.extrapolateBtn.setEnabled(False)
130        self.transformBtn.clicked.connect(self.transform)
131        self.transformBtn.setEnabled(False)
132
133        self.calculateBgBtn.clicked.connect(self.calculate_background)
134        self.calculateBgBtn.setEnabled(False)
135
136        self.model.itemChanged.connect(self.model_changed)
137
138        self.trigger.connect(self.finish_transform)
139
140    def setup_model(self):
141        """Populate the model with default data."""
142        self.model.setItem(W.W_QMIN,
143                           QtGui.QStandardItem("0.01"))
144        self.model.setItem(W.W_QMAX,
145                           QtGui.QStandardItem("0.20"))
146        self.model.setItem(W.W_QCUTOFF,
147                           QtGui.QStandardItem("0.22"))
148        self.model.setItem(W.W_BACKGROUND,
149                           QtGui.QStandardItem("0"))
150        self.model.setItem(W.W_TRANSFORM,
151                           QtGui.QStandardItem("Fourier"))
152        self.model.setItem(W.W_GUINIERA,
153                           QtGui.QStandardItem("0.0"))
154        self.model.setItem(W.W_GUINIERB,
155                           QtGui.QStandardItem("0.0"))
156        self.model.setItem(W.W_PORODK,
157                           QtGui.QStandardItem("0.0"))
158        self.model.setItem(W.W_PORODSIGMA,
159                           QtGui.QStandardItem("0.0"))
160        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(str(0)))
161        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(str(0)))
162        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(str(0)))
163        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(str(0)))
164        self.model.setItem(W.W_POLY, QtGui.QStandardItem(str(0)))
165        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(str(0)))
166
167    def model_changed(self, _):
168        """Actions to perform when the data is updated"""
169        if not self.mapper:
170            return
171        self.mapper.toFirst()
172        self._canvas.draw_q_space()
173
174    def _update_calculator(self):
175        self._calculator.lowerq = float(self.model.item(W.W_QMIN).text())
176        qmax1 = float(self.model.item(W.W_QMAX).text())
177        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
178        self._calculator.upperq = (qmax1, qmax2)
179        self._calculator.background = \
180            float(self.model.item(W.W_BACKGROUND).text())
181
182    def extrapolate(self):
183        """Extend the experiemntal data with guinier and porod curves."""
184        self._update_calculator()
185        params, extrapolation, _ = self._calculator.compute_extrapolation()
186
187        self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem(str(params['A'])))
188        self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem(str(params['B'])))
189        self.model.setItem(W.W_PORODK, QtGui.QStandardItem(str(params['K'])))
190        self.model.setItem(W.W_PORODSIGMA,
191                           QtGui.QStandardItem(str(params['sigma'])))
192
193        self._canvas.extrap = extrapolation
194        self._canvas.draw_q_space()
195        self.transformBtn.setEnabled(True)
196
197    def transform(self):
198        """Calculate the real space version of the extrapolation."""
199        method = str(self.model.item(W.W_TRANSFORM).text()).lower()
200
201        extrap = self._canvas.extrap
202        background = float(self.model.item(W.W_BACKGROUND).text())
203
204        def updatefn(msg):
205            """Report progress of transformation."""
206            self.communicate.statusBarUpdateSignal.emit(msg)
207
208        def completefn(transforms):
209            """Extract the values from the transforms and plot"""
210            self.trigger.emit(transforms)
211
212        self._update_calculator()
213        self._calculator.compute_transform(extrap, method, background,
214                                           completefn, updatefn)
215
216
217    def finish_transform(self, transforms):
218        transforms = transforms.toPyObject()
219        print(transforms)
220        params = self._calculator.extract_parameters(transforms[0])
221        self.model.setItem(W.W_CORETHICK,
222                           QtGui.QStandardItem(str(params['d0'])))
223        self.model.setItem(W.W_INTTHICK,
224                           QtGui.QStandardItem(str(params['dtr'])))
225        self.model.setItem(W.W_HARDBLOCK,
226                           QtGui.QStandardItem(str(params['Lc'])))
227        self.model.setItem(W.W_CRYSTAL,
228                           QtGui.QStandardItem(str(params['fill'])))
229        self.model.setItem(W.W_POLY,
230                           QtGui.QStandardItem(str(params['A'])))
231        self.model.setItem(W.W_PERIOD,
232                           QtGui.QStandardItem(str(params['max'])))
233        self._realplot.data = transforms
234        self._realplot.draw_real_space()
235
236    def setup_mapper(self):
237        """Creating mapping between model and gui elements."""
238        self.mapper = QtGui.QDataWidgetMapper(self)
239        self.mapper.setOrientation(QtCore.Qt.Vertical)
240        self.mapper.setModel(self.model)
241
242        self.mapper.addMapping(self.qMin, W.W_QMIN)
243        self.mapper.addMapping(self.qMax1, W.W_QMAX)
244        self.mapper.addMapping(self.qMax2, W.W_QCUTOFF)
245        self.mapper.addMapping(self.bg, W.W_BACKGROUND)
246        self.mapper.addMapping(self.transformCombo, W.W_TRANSFORM)
247
248        self.mapper.addMapping(self.guinierA, W.W_GUINIERA)
249        self.mapper.addMapping(self.guinierB, W.W_GUINIERB)
250        self.mapper.addMapping(self.porodK, W.W_PORODK)
251        self.mapper.addMapping(self.porodSigma, W.W_PORODSIGMA)
252
253        self.mapper.addMapping(self.avgCoreThick, W.W_CORETHICK)
254        self.mapper.addMapping(self.avgIntThick, W.W_INTTHICK)
255        self.mapper.addMapping(self.avgHardBlock, W.W_HARDBLOCK)
256        self.mapper.addMapping(self.polydisp, W.W_POLY)
257        self.mapper.addMapping(self.longPeriod, W.W_PERIOD)
258        self.mapper.addMapping(self.localCrystal, W.W_CRYSTAL)
259
260        self.mapper.toFirst()
261
262    def calculate_background(self):
263        """Find a good estimate of the background value."""
264        self._update_calculator()
265        background = self._calculator.compute_background()
266        temp = QtGui.QStandardItem(str(background))
267        self.model.setItem(W.W_BACKGROUND, temp)
268
269    # pylint: disable=invalid-name
270    @staticmethod
271    def allowBatch():
272        """
273        We cannot perform corfunc analysis in batch at this time.
274        """
275        return False
276
277    def setData(self, data_item, is_batch=False):
278        """
279        Obtain a QStandardItem object and dissect it to get Data1D/2D
280        Pass it over to the calculator
281        """
282        if not isinstance(data_item, list):
283            msg = "Incorrect type passed to the Corfunc Perpsective"
284            raise AttributeError(msg)
285
286        if not isinstance(data_item[0], QtGui.QStandardItem):
287            msg = "Incorrect type passed to the Corfunc Perspective"
288            raise AttributeError(msg)
289
290        model_item = data_item[0]
291        data = GuiUtils.dataFromItem(model_item)
292        self._calculator.set_data(data)
293        self.calculateBgBtn.setEnabled(True)
294        self.extrapolateBtn.setEnabled(True)
295
296        self._canvas.data = data
297        self._canvas.draw_q_space()
298
299        # self.model.item(WIDGETS.W_FILENAME).setData(QtCoreQVariant(self._model_item.text()))
300
301    def setClosable(self, value=True):
302        """
303        Allow outsiders close this widget
304        """
305        assert isinstance(value, bool)
306
307        self._allow_close = value
308    # pylint: enable=invalid-name
309
310
311if __name__ == "__main__":
312    APPLICATION = QtGui.QApplication([])
313    import qt4reactor
314    qt4reactor.install()
315    # DO NOT move the following import to the top!
316    # (unless you know what you're doing)
317    from twisted.internet import reactor
318    DIALOG = CorfuncWindow(reactor)
319    DIALOG.show()
320    reactor.run()
Note: See TracBrowser for help on using the repository browser.