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

Last change on this file since b320813 was b320813, checked in by Adam Washington <adam.washington@…>, 6 years ago

pyqt understands tuples, so I should be using them.

  • Property mode set to 100644
File size: 11.6 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(tuple)
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        params = self._calculator.extract_parameters(transforms[0])
219        self.model.setItem(W.W_CORETHICK,
220                           QtGui.QStandardItem(str(params['d0'])))
221        self.model.setItem(W.W_INTTHICK,
222                           QtGui.QStandardItem(str(params['dtr'])))
223        self.model.setItem(W.W_HARDBLOCK,
224                           QtGui.QStandardItem(str(params['Lc'])))
225        self.model.setItem(W.W_CRYSTAL,
226                           QtGui.QStandardItem(str(params['fill'])))
227        self.model.setItem(W.W_POLY,
228                           QtGui.QStandardItem(str(params['A'])))
229        self.model.setItem(W.W_PERIOD,
230                           QtGui.QStandardItem(str(params['max'])))
231        self._realplot.data = transforms
232        self._realplot.draw_real_space()
233
234    def setup_mapper(self):
235        """Creating mapping between model and gui elements."""
236        self.mapper = QtGui.QDataWidgetMapper(self)
237        self.mapper.setOrientation(QtCore.Qt.Vertical)
238        self.mapper.setModel(self.model)
239
240        self.mapper.addMapping(self.qMin, W.W_QMIN)
241        self.mapper.addMapping(self.qMax1, W.W_QMAX)
242        self.mapper.addMapping(self.qMax2, W.W_QCUTOFF)
243        self.mapper.addMapping(self.bg, W.W_BACKGROUND)
244        self.mapper.addMapping(self.transformCombo, W.W_TRANSFORM)
245
246        self.mapper.addMapping(self.guinierA, W.W_GUINIERA)
247        self.mapper.addMapping(self.guinierB, W.W_GUINIERB)
248        self.mapper.addMapping(self.porodK, W.W_PORODK)
249        self.mapper.addMapping(self.porodSigma, W.W_PORODSIGMA)
250
251        self.mapper.addMapping(self.avgCoreThick, W.W_CORETHICK)
252        self.mapper.addMapping(self.avgIntThick, W.W_INTTHICK)
253        self.mapper.addMapping(self.avgHardBlock, W.W_HARDBLOCK)
254        self.mapper.addMapping(self.polydisp, W.W_POLY)
255        self.mapper.addMapping(self.longPeriod, W.W_PERIOD)
256        self.mapper.addMapping(self.localCrystal, W.W_CRYSTAL)
257
258        self.mapper.toFirst()
259
260    def calculate_background(self):
261        """Find a good estimate of the background value."""
262        self._update_calculator()
263        background = self._calculator.compute_background()
264        temp = QtGui.QStandardItem(str(background))
265        self.model.setItem(W.W_BACKGROUND, temp)
266
267    # pylint: disable=invalid-name
268    @staticmethod
269    def allowBatch():
270        """
271        We cannot perform corfunc analysis in batch at this time.
272        """
273        return False
274
275    def setData(self, data_item, is_batch=False):
276        """
277        Obtain a QStandardItem object and dissect it to get Data1D/2D
278        Pass it over to the calculator
279        """
280        if not isinstance(data_item, list):
281            msg = "Incorrect type passed to the Corfunc Perpsective"
282            raise AttributeError(msg)
283
284        if not isinstance(data_item[0], QtGui.QStandardItem):
285            msg = "Incorrect type passed to the Corfunc Perspective"
286            raise AttributeError(msg)
287
288        model_item = data_item[0]
289        data = GuiUtils.dataFromItem(model_item)
290        self._calculator.set_data(data)
291        self.calculateBgBtn.setEnabled(True)
292        self.extrapolateBtn.setEnabled(True)
293
294        self._canvas.data = data
295        self._canvas.draw_q_space()
296
297        # self.model.item(WIDGETS.W_FILENAME).setData(QtCoreQVariant(self._model_item.text()))
298
299    def setClosable(self, value=True):
300        """
301        Allow outsiders close this widget
302        """
303        assert isinstance(value, bool)
304
305        self._allow_close = value
306    # pylint: enable=invalid-name
307
308
309if __name__ == "__main__":
310    APPLICATION = QtGui.QApplication([])
311    import qt4reactor
312    qt4reactor.install()
313    # DO NOT move the following import to the top!
314    # (unless you know what you're doing)
315    from twisted.internet import reactor
316    DIALOG = CorfuncWindow(reactor)
317    DIALOG.show()
318    reactor.run()
Note: See TracBrowser for help on using the repository browser.