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

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 e02d8f6 was e02d8f6, checked in by lewis, 8 years ago

Implement saving/loading state to/from a .svs project file

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