source: sasview/src/sas/sasgui/perspectives/corfunc/corfunc_panel.py @ 9c90cf3

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 9c90cf3 was 9c90cf3, checked in by lewis, 8 years ago

Minor refactor and add some comments

  • Property mode set to 100644
File size: 13.7 KB
Line 
1import wx
2import sys
3from wx.lib.scrolledpanel import ScrolledPanel
4from sas.sasgui.guiframe.events import PlotQrangeEvent
5from sas.sasgui.guiframe.events import StatusEvent
6from sas.sasgui.guiframe.panel_base import PanelBase
7from sas.sasgui.guiframe.utils import check_float
8from sas.sasgui.perspectives.invariant.invariant_widgets import OutputTextCtrl
9from sas.sasgui.perspectives.invariant.invariant_widgets import InvTextCtrl
10from sas.sasgui.perspectives.fitting.basepage import ModelTextCtrl
11from sas.sasgui.perspectives.corfunc.corfunc_state import CorfuncState
12
13if sys.platform.count("win32") > 0:
14    _STATICBOX_WIDTH = 350
15    PANEL_WIDTH = 400
16    PANEL_HEIGHT = 700
17    FONT_VARIANT = 0
18else:
19    _STATICBOX_WIDTH = 390
20    PANEL_WIDTH = 430
21    PANEL_HEIGHT = 700
22    FONT_VARIANT = 1
23
24class CorfuncPanel(ScrolledPanel,PanelBase):
25    window_name = "Correlation Function"
26    window_caption = "Correlation Function"
27    CENTER_PANE = True
28
29    def __init__(self, parent, data=None, manager=None, *args, **kwds):
30        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGHT)
31        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
32        ScrolledPanel.__init__(self, parent=parent, *args, **kwds)
33        PanelBase.__init__(self, parent)
34        self.SetupScrolling()
35        self.SetWindowVariant(variant=FONT_VARIANT)
36        self._manager = manager
37        self._data = data # The data to be analysed
38        self._data_name_box = None # Text box to show name of file
39        self._qmin_input = None
40        self._qmax1_input = None
41        self._qmax2_input = None
42        self.qmin = 0
43        self.qmax = (0, 0)
44        # Dictionary for saving IDs of text boxes used to display output data
45        self._output_ids = None
46        self.state = None
47        self._do_layout()
48        self.set_state()
49        self._qmin_input.Bind(wx.EVT_TEXT, self._on_enter_qrange)
50        self._qmax1_input.Bind(wx.EVT_TEXT, self._on_enter_qrange)
51        self._qmax2_input.Bind(wx.EVT_TEXT, self._on_enter_qrange)
52
53    def set_state(self, state=None, data=None):
54        """
55        Set the state of the panel. If no state is provided, the panel will
56        be set to the default state.
57
58        :param state: A CorfuncState object
59        :param data: A Data1D object
60        """
61        if state is None:
62            self.state = CorfuncState()
63        else:
64            self.state = state
65        if data is not None:
66            self.state.data = data
67        self.set_data(self.state.data, set_qrange=False)
68        if self.state.qmin is not None:
69            self.set_qmin(self.state.qmin)
70        if self.state.qmax is not None and self.state.qmax != (None, None):
71            self.set_qmax(tuple(self.state.qmax))
72
73    def get_state(self):
74        """
75        Return the state of the panel
76        """
77        state = CorfuncState()
78        state.set_saved_state('qmin_tcl', self.qmin)
79        state.set_saved_state('qmax1_tcl', self.qmax[0])
80        state.set_saved_state('qmax2_tcl', self.qmax[1])
81        if self._data is not None:
82            state.file = self._data.title
83            state.data = self._data
84        self.state = state
85
86        return self.state
87
88    def onSetFocus(self, evt):
89        if evt is not None:
90            evt.Skip()
91        self._validate_qrange()
92
93    def set_data(self, data=None, set_qrange=True):
94        """
95        Update the GUI to reflect new data that has been loaded in
96
97        :param data: The data that has been loaded
98        """
99        if data is None:
100            return
101        self._data_name_box.SetValue(str(data.title))
102        self._data = data
103        if self._manager is not None:
104            self._manager.show_data(data=data, reset=True)
105        if set_qrange:
106            lower = data.x[-1]*0.05
107            upper1 = data.x[-1] - lower*5
108            upper2 = data.x[-1]
109            self.set_qmin(lower)
110            self.set_qmax((upper1, upper2))
111
112    def get_data(self):
113        return self._data
114
115    def save_project(self, doc=None):
116        """
117        Return an XML node containing the state of the panel
118
119        :param doc: Am xml node to attach the project state to (optional)
120        """
121        data = self._data
122        state = self.get_state()
123        if data is not None:
124            new_doc, sasentry = self._manager.state_reader._to_xml_doc(data)
125            new_doc = state.toXML(doc=new_doc, entry_node=sasentry)
126            if new_doc is not None:
127                if doc is not None and hasattr(doc, "firstChild"):
128                    child = new_doc.getElementsByTagName("SASentry")
129                    for item in child:
130                        doc.firstChild.appendChild(item)
131                else:
132                    doc = new_doc
133        return doc
134
135    def set_qmin(self, qmin):
136        self.qmin = qmin
137        self._qmin_input.SetValue(str(qmin))
138
139    def set_qmax(self, qmax):
140        self.qmax = qmax
141        self._qmax1_input.SetValue(str(qmax[0]))
142        self._qmax2_input.SetValue(str(qmax[1]))
143
144
145    def _on_enter_qrange(self, event=None):
146        """
147        Read values from input boxes and save to memory.
148        """
149        if event is not None: event.Skip()
150        if not self._validate_qrange():
151            return
152        new_qmin = float(self._qmin_input.GetValue())
153        new_qmax1 = float(self._qmax1_input.GetValue())
154        new_qmax2 = float(self._qmax2_input.GetValue())
155        self.qmin = new_qmin
156        self.qmax = (new_qmax1, new_qmax2)
157        data_id = self._manager.data_id
158        from sas.sasgui.perspectives.corfunc.corfunc import GROUP_ID_IQ_DATA
159        group_id = GROUP_ID_IQ_DATA
160        if event is not None:
161            wx.PostEvent(self._manager.parent, PlotQrangeEvent(
162                ctrl=[self._qmin_input, self._qmax1_input, self._qmax2_input],
163                active=event.GetEventObject(), id=data_id, group_id=group_id,
164                leftdown=False))
165
166    def _validate_qrange(self):
167        """
168        Check that the values for qmin and qmax in the input boxes are valid
169        """
170        if self._data is None:
171            return False
172        qmin_valid = check_float(self._qmin_input)
173        qmax1_valid = check_float(self._qmax1_input)
174        qmax2_valid = check_float(self._qmax2_input)
175        qmax_valid = qmax1_valid and qmax2_valid
176        if not (qmin_valid and qmax_valid):
177            if not qmin_valid:
178                self._qmin_input.SetBackgroundColour('pink')
179                self._qmin_input.Refresh()
180            if not qmax1_valid:
181                self._qmax1_input.SetBackgroundColour('pink')
182                self._qmax1_input.Refresh()
183            if not qmax2_valid:
184                self._qmax2_input.SetBackgroundColour('pink')
185                self._qmax2_input.Refresh()
186            return False
187        qmin = float(self._qmin_input.GetValue())
188        qmax1 = float(self._qmax1_input.GetValue())
189        qmax2 = float(self._qmax2_input.GetValue())
190        msg = ""
191        if not qmin > self._data.x.min():
192            msg = "qmin must be greater than the lowest q value"
193            qmin_valid = False
194        elif qmax2 < qmax1:
195            "qmax1 must be less than qmax2"
196            qmax_valid = False
197        elif qmin > qmax1:
198            "qmin must be less than qmax"
199            qmin_valid = False
200
201        if not (qmin_valid and qmax_valid):
202            if not qmin_valid:
203                self._qmin_input.SetBackgroundColour('pink')
204            if not qmax_valid:
205                self._qmax1_input.SetBackgroundColour('pink')
206                self._qmax2_input.SetBackgroundColour('pink')
207            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
208        else:
209            self._qmin_input.SetBackgroundColour(wx.WHITE)
210            self._qmax1_input.SetBackgroundColour(wx.WHITE)
211            self._qmax2_input.SetBackgroundColour(wx.WHITE)
212        self._qmin_input.Refresh()
213        self._qmax1_input.Refresh()
214        self._qmax2_input.Refresh()
215        return (qmin_valid and qmax_valid)
216
217    def _do_layout(self):
218        """
219        Draw the window content
220        """
221        vbox = wx.GridBagSizer(0,0)
222
223        # I(q) data box
224        databox = wx.StaticBox(self, -1, "I(Q) Data Source")
225        databox_sizer = wx.StaticBoxSizer(databox, wx.VERTICAL)
226
227        file_sizer = wx.GridBagSizer(5, 5)
228
229        file_name_label = wx.StaticText(self, -1, "Name:")
230        file_sizer.Add(file_name_label, (0, 0), (1, 1),
231            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
232
233        self._data_name_box = OutputTextCtrl(self, -1,
234            size=(300,20))
235        file_sizer.Add(self._data_name_box, (0, 1), (1, 1),
236            wx.CENTER | wx.ADJUST_MINSIZE, 15)
237
238        file_sizer.AddSpacer((1, 25), pos=(0,2))
239        databox_sizer.Add(file_sizer, wx.TOP, 15)
240
241        vbox.Add(databox_sizer, (0, 0), (1, 1),
242            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE | wx.TOP, 15)
243
244
245        # Parameters
246        qbox = wx.StaticBox(self, -1, "Parameters")
247        qbox_sizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
248        qbox_sizer.SetMinSize((_STATICBOX_WIDTH, 75))
249
250        q_sizer = wx.GridBagSizer(5, 5)
251
252        # Explanation
253        explanation_txt = ("Corfunc will use all values in the lower range for"
254            " Guinier back extrapolation, and all values in the upper range "
255            "for Porod forward extrapolation.")
256        explanation_label = wx.StaticText(self, -1, explanation_txt,
257            size=(_STATICBOX_WIDTH, 60))
258
259        q_sizer.Add(explanation_label, (0,0), (1,4), wx.LEFT | wx.EXPAND, 5)
260
261        qrange_label = wx.StaticText(self, -1, "Q Range:", size=(50,20))
262        q_sizer.Add(qrange_label, (1,0), (1,1), wx.LEFT | wx.EXPAND, 5)
263
264        # Lower Q Range
265        qmin_label = wx.StaticText(self, -1, "Lower:", size=(50,20))
266        qmin_dash_label = wx.StaticText(self, -1, "-", size=(10,20),
267            style=wx.ALIGN_CENTER_HORIZONTAL)
268
269        qmin_lower = OutputTextCtrl(self, -1, size=(50, 20), value="0.0")
270        self._qmin_input = ModelTextCtrl(self, -1, size=(50, 20),
271                        style=wx.TE_PROCESS_ENTER, name='qmin_input',
272                        text_enter_callback=self._on_enter_qrange)
273        self._qmin_input.SetToolTipString(("Values with q < qmin will be used "
274            "for Guinier back extrapolation"))
275
276        q_sizer.Add(qmin_label, (2, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
277        q_sizer.Add(qmin_lower, (2, 1), (1, 1), wx.LEFT, 5)
278        q_sizer.Add(qmin_dash_label, (2, 2), (1, 1), wx.CENTER | wx.EXPAND, 5)
279        q_sizer.Add(self._qmin_input, (2, 3), (1, 1), wx.LEFT, 5)
280
281        # Upper Q range
282        qmax_tooltip = ("Values with qmax1 < q < qmax2 will be used for Porod"
283            " forward extrapolation")
284
285        qmax_label = wx.StaticText(self, -1, "Upper:", size=(50,20))
286        qmax_dash_label = wx.StaticText(self, -1, "-", size=(10,20),
287            style=wx.ALIGN_CENTER_HORIZONTAL)
288
289        self._qmax1_input = ModelTextCtrl(self, -1, size=(50, 20),
290            style=wx.TE_PROCESS_ENTER, name="qmax1_input",
291            text_enter_callback=self._on_enter_qrange)
292        self._qmax1_input.SetToolTipString(qmax_tooltip)
293        self._qmax2_input = ModelTextCtrl(self, -1, size=(50, 20),
294            style=wx.TE_PROCESS_ENTER, name="qmax2_input",
295            text_enter_callback=self._on_enter_qrange)
296        self._qmax2_input.SetToolTipString(qmax_tooltip)
297
298        q_sizer.Add(qmax_label, (3, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
299        q_sizer.Add(self._qmax1_input, (3, 1), (1, 1), wx.LEFT, 5)
300        q_sizer.Add(qmax_dash_label, (3, 2), (1, 1), wx.CENTER | wx.EXPAND, 5)
301        q_sizer.Add(self._qmax2_input, (3,3), (1, 1), wx.LEFT, 5)
302
303        qbox_sizer.Add(q_sizer, wx.TOP, 0)
304
305        vbox.Add(qbox_sizer, (1, 0), (1, 1),
306            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
307
308        # Output data
309        outputbox = wx.StaticBox(self, -1, "Output Measuments")
310        outputbox_sizer = wx.StaticBoxSizer(outputbox, wx.VERTICAL)
311
312        output_sizer = wx.GridBagSizer(5, 5)
313
314        label_strings = [
315            "Long Period (A): ",
316            "Average Hard Block Thickness (A): ",
317            "Average Interface Thickness (A): ",
318            "Average Core Thickness: ",
319            "PolyDispersity: ",
320            "Filling Fraction: "
321        ]
322        self._output_ids = dict()
323        for i in range(len(label_strings)):
324            # Create a label and a text box for each poperty
325            label = wx.StaticText(self, -1, label_strings[i])
326            output_box = OutputTextCtrl(self, wx.NewId(), size=(50, 20),
327                value="-", style=wx.ALIGN_CENTER_HORIZONTAL)
328            # Save the ID of each of the text boxes for accessing after the
329            # output data has been calculated
330            self._output_ids[label_strings[i]] = output_box.GetId()
331            output_sizer.Add(label, (i, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
332            output_sizer.Add(output_box, (i, 2), (1, 1),
333                wx.RIGHT | wx.EXPAND, 15)
334
335        outputbox_sizer.Add(output_sizer, wx.TOP, 0)
336
337        vbox.Add(outputbox_sizer, (2, 0), (1, 1),
338            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
339
340        # Controls
341        controlbox = wx.StaticBox(self, -1, "Controls")
342        controlbox_sizer = wx.StaticBoxSizer(controlbox, wx.VERTICAL)
343
344        controls_sizer = wx.BoxSizer(wx.VERTICAL)
345
346        extrapolate_btn = wx.Button(self, wx.NewId(), "Extrapolate")
347        transform_btn = wx.Button(self, wx.NewId(), "Transform")
348        compute_btn = wx.Button(self, wx.NewId(), "Compute Measuments")
349
350        controls_sizer.Add(extrapolate_btn, wx.CENTER | wx.EXPAND)
351        controls_sizer.Add(transform_btn, wx.CENTER | wx.EXPAND)
352        controls_sizer.Add(compute_btn, wx.CENTER | wx.EXPAND)
353
354        controlbox_sizer.Add(controls_sizer, wx.TOP | wx.EXPAND, 0)
355        vbox.Add(controlbox_sizer, (3, 0), (1, 1),
356            wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
357
358        self.SetSizer(vbox)
Note: See TracBrowser for help on using the repository browser.