source: sasview/src/sas/sasgui/perspectives/pr/inversion_panel.py @ bacc04b

Last change on this file since bacc04b was 18b7ecb9, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Save Analysis improvements. Fixed #629

  • Property mode set to 100644
File size: 38.1 KB
Line 
1#!/usr/bin/env python
2
3# version
4__id__ = "$Id: aboutdialog.py 1193 2007-05-03 17:29:59Z dmitriy $"
5__revision__ = "$Revision: 1193 $"
6
7import wx
8import os
9import sys
10import logging
11from wx.lib.scrolledpanel import ScrolledPanel
12from sas.sasgui.guiframe.events import StatusEvent
13from sas.sasgui.guiframe.panel_base import PanelBase
14from inversion_state import InversionState
15from pr_widgets import PrTextCtrl
16from pr_widgets import DataFileTextCtrl
17from pr_widgets import OutputTextCtrl
18from sas.sasgui.guiframe.documentation_window import DocumentationWindow
19
20if sys.platform.count("win32") > 0:
21    FONT_VARIANT = 0
22else:
23    FONT_VARIANT = 1
24
25class InversionControl(ScrolledPanel, PanelBase):
26    """
27    """
28    window_name = 'pr_control'
29    window_caption = "P(r) control panel"
30    CENTER_PANE = True
31
32    # Figure of merit parameters [default]
33
34    ## Oscillation parameters (sin function = 1.1)
35    oscillation_max = 1.5
36
37    def __init__(self, parent, id=-1, plots=None, **kwargs):
38        """
39        """
40        ScrolledPanel.__init__(self, parent, id=id, **kwargs)
41        PanelBase.__init__(self, parent)
42        self.SetupScrolling()
43        #Set window's font size
44        self.SetWindowVariant(variant=FONT_VARIANT)
45        self._set_analysis(False)
46
47        self.plots = plots
48        self.radio_buttons = {}
49        self.parent = parent.parent
50
51        ## Data file TextCtrl
52        self.data_file = None
53        self.plot_data = None
54        self.nfunc_ctl = None
55        self.alpha_ctl = None
56        self.dmax_ctl = None
57        self.time_ctl = None
58        self.chi2_ctl = None
59        self.osc_ctl = None
60        self.file_radio = None
61        self.plot_radio = None
62        self.label_sugg = None
63        self.qmin_ctl = None
64        self.qmax_ctl = None
65        self.swidth_ctl = None
66        self.sheight_ctl = None
67
68        self.rg_ctl = None
69        self.iq0_ctl = None
70        self.bck_chk = None
71        self.bck_ctl = None
72
73        # TextCtrl for fraction of positive P(r)
74        self.pos_ctl = None
75
76        # TextCtrl for fraction of 1 sigma positive P(r)
77        self.pos_err_ctl = None
78
79        ## Estimates
80        self.alpha_estimate_ctl = None
81        self.nterms_estimate_ctl = None
82        ## D_max distance explorator
83        self.distance_explorator_ctl = None
84        ## Data manager
85        self._manager = None
86        ## Default file location for save
87        self._default_save_location = os.getcwd()
88        if self.parent is not None:
89            self._default_save_location = \
90                        self.parent._default_save_location
91
92        # Default width
93        self._default_width = 350
94        self._do_layout()
95
96    def __setattr__(self, name, value):
97        """
98        Allow direct hooks to text boxes
99        """
100        if name == 'nfunc':
101            self.nfunc_ctl.SetValue(str(int(value)))
102        elif name == 'd_max':
103            self.dmax_ctl.SetValue(str(value))
104        elif name == 'alpha':
105            self.alpha_ctl.SetValue(str(value))
106        elif name == 'chi2':
107            self.chi2_ctl.SetValue("%-5.2g" % value)
108        elif name == 'bck':
109            self.bck_ctl.SetValue("%-5.2g" % value)
110        elif name == 'q_min':
111            self.qmin_ctl.SetValue("%-5.2g" % value)
112        elif name == 'q_max':
113            self.qmax_ctl.SetValue("%-5.2g" % value)
114        elif name == 'elapsed':
115            self.time_ctl.SetValue("%-5.2g" % value)
116        elif name == 'rg':
117            self.rg_ctl.SetValue("%-5.2g" % value)
118        elif name == 'iq0':
119            self.iq0_ctl.SetValue("%-5.2g" % value)
120        elif name == 'oscillation':
121            self.osc_ctl.SetValue("%-5.2g" % value)
122        elif name == 'slit_width':
123            self.swidth_ctl.SetValue("%-5.2g" % value)
124        elif name == 'slit_height':
125            self.sheight_ctl.SetValue("%-5.2g" % value)
126        elif name == 'positive':
127            self.pos_ctl.SetValue("%-5.2g" % value)
128        elif name == 'pos_err':
129            self.pos_err_ctl.SetValue("%-5.2g" % value)
130        elif name == 'alpha_estimate':
131            self.alpha_estimate_ctl.SetToolTipString("Click to accept value.")
132            self.alpha_estimate_ctl.Enable(True)
133            self.alpha_estimate_ctl.SetLabel("%-3.1g" % value)
134            #self.alpha_estimate_ctl.Show()
135            #self.label_sugg.Show()
136        elif name == 'nterms_estimate':
137            self.nterms_estimate_ctl.SetToolTipString("Click to accept value.")
138            self.nterms_estimate_ctl.Enable(True)
139            self.nterms_estimate_ctl.SetLabel("%-g" % value)
140        elif name == 'plotname':
141            self.plot_data.SetValue(str(value))
142            self._on_pars_changed(None)
143        elif name == 'datafile':
144            self.plot_data.SetValue(str(value))
145            self._on_pars_changed(None)
146        else:
147            wx.Panel.__setattr__(self, name, value)
148
149    def __getattr__(self, name):
150        """
151        Allow direct hooks to text boxes
152        """
153        if name == 'nfunc':
154            try:
155                return int(self.nfunc_ctl.GetValue())
156            except:
157                return -1
158        elif name == 'd_max':
159            try:
160                return self.dmax_ctl.GetValue()
161            except:
162                return -1.0
163        elif name == 'alpha':
164            try:
165                return self.alpha_ctl.GetValue()
166            except:
167                return -1.0
168        elif name == 'chi2':
169            try:
170                return float(self.chi2_ctl.GetValue())
171            except:
172                return None
173        elif name == 'bck':
174            try:
175                return float(self.bck_ctl.GetValue())
176            except:
177                return None
178        elif name == 'q_min':
179            try:
180                return float(self.qmin_ctl.GetValue())
181            except:
182                return 0.0
183        elif name == 'q_max':
184            try:
185                return float(self.qmax_ctl.GetValue())
186            except:
187                return 0.0
188        elif name == 'elapsed':
189            try:
190                return float(self.time_ctl.GetValue())
191            except:
192                return None
193        elif name == 'rg':
194            try:
195                return float(self.rg_ctl.GetValue())
196            except:
197                return None
198        elif name == 'iq0':
199            try:
200                return float(self.iq0_ctl.GetValue())
201            except:
202                return None
203        elif name == 'oscillation':
204            try:
205                return float(self.osc_ctl.GetValue())
206            except:
207                return None
208        elif name == 'slit_width':
209            try:
210                return float(self.swidth_ctl.GetValue())
211            except:
212                return None
213        elif name == 'slit_height':
214            try:
215                return float(self.sheight_ctl.GetValue())
216            except:
217                return None
218        elif name == 'pos':
219            try:
220                return float(self.pos_ctl.GetValue())
221            except:
222                return None
223        elif name == 'pos_err':
224            try:
225                return float(self.pos_err_ctl.GetValue())
226            except:
227                return None
228        elif name == 'alpha_estimate':
229            try:
230                return float(self.alpha_estimate_ctl.GetLabel())
231            except:
232                return None
233        elif name == 'nterms_estimate':
234            try:
235                return int(self.nterms_estimate_ctl.GetLabel())
236            except:
237                return None
238        elif name == 'plotname':
239            return self.plot_data.GetValue()
240        elif name == 'datafile':
241            return self.plot_data.GetValue()
242        else:
243            return wx.Panel.__getattribute__(self, name)
244
245    def save_project(self, doc=None):
246        """
247        return an xml node containing state of the panel
248         that guiframe can write to file
249        """
250        data = self.get_data()
251        state = self.get_state()
252        if data is not None:
253            new_doc = self._manager.state_reader.write_toXML(data, state)
254            if new_doc is not None:
255                if doc is not None and hasattr(doc, "firstChild"):
256                    child = new_doc.getElementsByTagName("SASentry")
257                    for item in child:
258                        doc.firstChild.appendChild(item)
259                else:
260                    doc = new_doc
261        return doc
262
263    def on_save(self, evt=None):
264        """
265        Method used to create a memento of the current state
266
267        :return: state object
268        """
269        # Ask the user the location of the file to write to.
270        path = None
271        if self.parent != None:
272            self._default_save_location = self.parent._default_save_location
273        dlg = wx.FileDialog(self, "Choose a file",
274                            self._default_save_location,
275                            self.window_caption, "*.prv", wx.SAVE)
276        if dlg.ShowModal() == wx.ID_OK:
277            path = dlg.GetPath()
278            self._default_save_location = os.path.dirname(path)
279            if self.parent != None:
280                self.parent._default_save_location = self._default_save_location
281        else:
282            return None
283
284        dlg.Destroy()
285
286        state = self.get_state()
287
288        # MAC always needs the extension for saving
289        extens = ".prv"
290        # Make sure the ext included in the file name
291        fName = os.path.splitext(path)[0] + extens
292        self._manager.save_data(filepath=fName, prstate=state)
293
294        return state
295
296    def get_data(self):
297        """
298        """
299        return self._manager.get_data()
300
301    def get_state(self):
302        """
303        Get the current state
304
305        : return: state object
306        """
307        # Construct the state object
308        state = InversionState()
309
310        # Read the panel's parameters
311        flag, alpha, dmax, nfunc, qmin, \
312        qmax, height, width = self._read_pars()
313
314        state.nfunc = nfunc
315        state.d_max = dmax
316        state.alpha = alpha
317        state.qmin = qmin
318        state.qmax = qmax
319        state.width = width
320        state.height = height
321
322        # Data file
323        state.file = self.plot_data.GetValue()
324
325        # Background evaluation checkbox
326        state.estimate_bck = self.bck_chk.IsChecked()
327
328        # Estimates
329        state.nterms_estimate = self.nterms_estimate
330        state.alpha_estimate = self.alpha_estimate
331
332        # Read the output values
333        state.chi2 = self.chi2
334        state.elapsed = self.elapsed
335        state.osc = self.oscillation
336        state.pos = self.pos
337        state.pos_err = self.pos_err
338        state.rg = self.rg
339        state.iq0 = self.iq0
340        state.bck = self.bck
341
342        return state
343
344    def set_state(self, state):
345        """
346        Set the state of the panel and inversion problem to
347        the state passed as a parameter.
348        Execute the inversion immediately after filling the
349        controls.
350
351        :param state: InversionState object
352        """
353        if state.nfunc is not None:
354            self.nfunc = state.nfunc
355        if state.d_max is not None:
356            self.d_max = state.d_max
357        if state.alpha is not None:
358            self.alpha = state.alpha
359        if state.qmin is not None:
360            self.q_min = state.qmin
361        if state.qmax is not None:
362            self.q_max = state.qmax
363        if state.width is not None:
364            self.slit_width = state.width
365        if state.height is not None:
366            self.slit_height = state.height
367
368        # Data file
369        self.plot_data.SetValue(str(state.file))
370
371        # Background evaluation checkbox
372        self.bck_chk.SetValue(state.estimate_bck)
373
374        # Estimates
375        if state.nterms_estimate is not None:
376            self.nterms_estimate = state.nterms_estimate
377        if state.alpha_estimate is not None:
378            self.alpha_estimate = state.alpha_estimate
379
380
381        # Read the output values
382        if state.chi2 is not None:
383            self.chi2 = state.chi2
384        if state.elapsed is not None:
385            self.elapsed = state.elapsed
386        if state.osc is not None:
387            self.oscillation = state.osc
388        if state.pos is not None:
389            self.positive = state.pos
390        if state.pos_err is not None:
391            self.pos_err = state.pos_err
392        if state.rg is not None:
393            self.rg = state.rg
394        if state.iq0 is not None:
395            self.iq0 = state.iq0
396        if state.bck is not None:
397            self.bck = state.bck
398
399        # We have the data available for serialization
400        self._set_analysis(True)
401
402        # Perform inversion
403        self._on_invert(None)
404
405    def set_manager(self, manager):
406        self._manager = manager
407        if manager is not None:
408            self._set_analysis(False)
409
410    def _do_layout(self):
411        vbox = wx.GridBagSizer(0, 0)
412        iy_vb = 0
413
414        # ----- I(q) data -----
415        databox = wx.StaticBox(self, -1, "I(q) data source")
416
417        boxsizer1 = wx.StaticBoxSizer(databox, wx.VERTICAL)
418        boxsizer1.SetMinSize((self._default_width, 50))
419        pars_sizer = wx.GridBagSizer(5, 5)
420
421        iy = 0
422        self.file_radio = wx.StaticText(self, -1, "Name:")
423        pars_sizer.Add(self.file_radio, (iy, 0), (1, 1),
424                       wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
425
426        self.plot_data = DataFileTextCtrl(self, -1, size=(260, 20))
427
428        pars_sizer.Add(self.plot_data, (iy, 1), (1, 1),
429                       wx.EXPAND | wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 15)
430
431        self.bck_chk = wx.CheckBox(self, -1, "Estimate background level")
432        hint_msg = "Check box to let the fit estimate "
433        hint_msg += "the constant background level."
434        self.bck_chk.SetToolTipString(hint_msg)
435        self.bck_chk.Bind(wx.EVT_CHECKBOX, self._on_pars_changed)
436        iy += 1
437        pars_sizer.Add(self.bck_chk, (iy, 0), (1, 2),
438                       wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
439        boxsizer1.Add(pars_sizer, 0, wx.EXPAND)
440        vbox.Add(boxsizer1, (iy_vb, 0), (1, 1),
441                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE | wx.TOP, 5)
442
443        # ----- Add slit parameters -----
444        if True:
445            sbox = wx.StaticBox(self, -1, "Slit parameters")
446            sboxsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
447            sboxsizer.SetMinSize((self._default_width, 20))
448
449            sizer_slit = wx.GridBagSizer(5, 5)
450
451            label_sheight = wx.StaticText(self, -1, "Height", size=(40, 20))
452            label_swidth = wx.StaticText(self, -1, "Width", size=(40, 20))
453            label_sunits2 = wx.StaticText(self, -1, "[A^(-1)]", size=(55, 20))
454            self.sheight_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
455            self.swidth_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
456            hint_msg = "Enter slit height in units of Q or leave blank."
457            self.sheight_ctl.SetToolTipString(hint_msg)
458            hint_msg = "Enter slit width in units of Q or leave blank."
459            self.swidth_ctl.SetToolTipString(hint_msg)
460
461            iy = 0
462            sizer_slit.Add(label_sheight, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
463            sizer_slit.Add(self.sheight_ctl, (iy, 1), (1, 1), wx.LEFT | wx.EXPAND, 5)
464            sizer_slit.Add(label_swidth, (iy, 2), (1, 1), wx.LEFT | wx.EXPAND, 5)
465            sizer_slit.Add(self.swidth_ctl, (iy, 3), (1, 1), wx.LEFT | wx.EXPAND, 5)
466            sizer_slit.Add(label_sunits2, (iy, 4), (1, 1), wx.LEFT | wx.EXPAND, 5)
467
468            sboxsizer.Add(sizer_slit, wx.TOP, 15)
469            iy_vb += 1
470            vbox.Add(sboxsizer, (iy_vb, 0), (1, 1),
471                     wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
472
473        # ----- Q range -----
474        qbox = wx.StaticBox(self, -1, "Q range")
475        qboxsizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
476        qboxsizer.SetMinSize((self._default_width, 20))
477
478        sizer_q = wx.GridBagSizer(5, 5)
479
480        label_qmin = wx.StaticText(self, -1, "Q min", size=(40, 20))
481        label_qmax = wx.StaticText(self, -1, "Q max", size=(40, 20))
482        label_qunits2 = wx.StaticText(self, -1, "[A^(-1)]", size=(55, 20))
483        self.qmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
484        self.qmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
485        hint_msg = "Select a lower bound for Q or leave blank."
486        self.qmin_ctl.SetToolTipString(hint_msg)
487        hint_msg = "Select an upper bound for Q or leave blank."
488        self.qmax_ctl.SetToolTipString(hint_msg)
489        self.qmin_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
490        self.qmax_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
491
492        iy = 0
493        sizer_q.Add(label_qmin, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 5)
494        sizer_q.Add(self.qmin_ctl, (iy, 1), (1, 1), wx.LEFT | wx.EXPAND, 5)
495        sizer_q.Add(label_qmax, (iy, 2), (1, 1), wx.LEFT | wx.EXPAND, 5)
496        sizer_q.Add(self.qmax_ctl, (iy, 3), (1, 1), wx.LEFT | wx.EXPAND, 5)
497        sizer_q.Add(label_qunits2, (iy, 4), (1, 1), wx.LEFT | wx.EXPAND, 5)
498        qboxsizer.Add(sizer_q, wx.TOP, 15)
499
500        iy_vb += 1
501        vbox.Add(qboxsizer, (iy_vb, 0), (1, 1),
502                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
503
504        # ----- Parameters -----
505        parsbox = wx.StaticBox(self, -1, "Parameters")
506        boxsizer2 = wx.StaticBoxSizer(parsbox, wx.VERTICAL)
507        boxsizer2.SetMinSize((self._default_width, 50))
508
509        explanation = "P(r) is found by fitting a set of base functions"
510        explanation += " to I(Q). The minimization involves"
511        explanation += " a regularization term to ensure a smooth P(r)."
512        explanation += " The regularization constant gives the size of that "
513        explanation += "term. The suggested value is the value above which the"
514        explanation += " output P(r) will have only one peak."
515        label_explain = wx.StaticText(self, -1, explanation, size=(280, 90))
516        boxsizer2.Add(label_explain, wx.LEFT | wx.BOTTOM, 5)
517
518        label_nfunc = wx.StaticText(self, -1, "Number of terms")
519        label_nfunc.SetMinSize((120, 20))
520        label_alpha = wx.StaticText(self, -1, "Regularization constant")
521        label_dmax = wx.StaticText(self, -1, "Max distance [A]")
522        self.label_sugg = wx.StaticText(self, -1, "Suggested value")
523
524        self.nfunc_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
525        self.nfunc_ctl.SetToolTipString("Number of terms in the expansion.")
526        self.alpha_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
527        hint_msg = "Control parameter for the size of the regularization term."
528        self.alpha_ctl.SetToolTipString(hint_msg)
529        self.dmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER, size=(60, 20))
530        hint_msg = "Maximum distance between any two points in the system."
531        self.dmax_ctl.SetToolTipString(hint_msg)
532        wx_id = wx.NewId()
533        self.alpha_estimate_ctl = wx.Button(self, wx_id, "")
534        self.Bind(wx.EVT_BUTTON, self._on_accept_alpha, id=wx_id)
535        self.alpha_estimate_ctl.Enable(False)
536        self.alpha_estimate_ctl.SetToolTipString("Waiting for estimate...")
537
538        wx_id = wx.NewId()
539        self.nterms_estimate_ctl = wx.Button(self, wx_id, "")
540        #self.nterms_estimate_ctl.Hide()
541        self.Bind(wx.EVT_BUTTON, self._on_accept_nterms, id=wx_id)
542        self.nterms_estimate_ctl.Enable(False)
543
544        self.nterms_estimate_ctl.SetToolTipString("Waiting for estimate...")
545
546        self.nfunc_ctl.Bind(wx.EVT_TEXT, self._read_pars)
547        self.alpha_ctl.Bind(wx.EVT_TEXT, self._read_pars)
548        self.dmax_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
549
550        # Distance explorator
551        wx_id = wx.NewId()
552        self.distance_explorator_ctl = wx.Button(self, wx_id, "Explore")
553        self.Bind(wx.EVT_BUTTON, self._on_explore, id=wx_id)
554
555
556        sizer_params = wx.GridBagSizer(5, 5)
557
558        iy = 0
559        sizer_params.Add(self.label_sugg, (iy, 2), (1, 1), wx.LEFT, 15)
560        iy += 1
561        sizer_params.Add(label_nfunc, (iy, 0), (1, 1), wx.LEFT, 15)
562        sizer_params.Add(self.nfunc_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
563        sizer_params.Add(self.nterms_estimate_ctl, (iy, 2), (1, 1), wx.LEFT, 15)
564        iy += 1
565        sizer_params.Add(label_alpha, (iy, 0), (1, 1), wx.LEFT, 15)
566        sizer_params.Add(self.alpha_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
567        sizer_params.Add(self.alpha_estimate_ctl, (iy, 2), (1, 1), wx.LEFT, 15)
568        iy += 1
569        sizer_params.Add(label_dmax, (iy, 0), (1, 1), wx.LEFT, 15)
570        sizer_params.Add(self.dmax_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
571        sizer_params.Add(self.distance_explorator_ctl, (iy, 2),
572                         (1, 1), wx.LEFT, 15)
573
574        boxsizer2.Add(sizer_params, 0)
575
576        iy_vb += 1
577        vbox.Add(boxsizer2, (iy_vb, 0), (1, 1),
578                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
579
580
581        # ----- Results -----
582        resbox = wx.StaticBox(self, -1, "Outputs")
583        ressizer = wx.StaticBoxSizer(resbox, wx.VERTICAL)
584        ressizer.SetMinSize((self._default_width, 50))
585
586        label_rg = wx.StaticText(self, -1, "Rg")
587        label_rg_unit = wx.StaticText(self, -1, "[A]")
588        label_iq0 = wx.StaticText(self, -1, "I(Q=0)")
589        label_iq0_unit = wx.StaticText(self, -1, "[A^(-1)]")
590        label_bck = wx.StaticText(self, -1, "Background")
591        label_bck_unit = wx.StaticText(self, -1, "[A^(-1)]")
592        self.rg_ctl = OutputTextCtrl(self, -1, size=(60, 20))
593        hint_msg = "Radius of gyration for the computed P(r)."
594        self.rg_ctl.SetToolTipString(hint_msg)
595        self.iq0_ctl = OutputTextCtrl(self, -1, size=(60, 20))
596        hint_msg = "Scattering intensity at Q=0 for the computed P(r)."
597        self.iq0_ctl.SetToolTipString(hint_msg)
598        self.bck_ctl = OutputTextCtrl(self, -1, size=(60, 20))
599        self.bck_ctl.SetToolTipString("Value of estimated constant background.")
600
601        label_time = wx.StaticText(self, -1, "Computation time")
602        label_time_unit = wx.StaticText(self, -1, "secs")
603        label_time.SetMinSize((120, 20))
604        label_chi2 = wx.StaticText(self, -1, "Chi2/dof")
605        label_osc = wx.StaticText(self, -1, "Oscillations")
606        label_pos = wx.StaticText(self, -1, "Positive fraction")
607        label_pos_err = wx.StaticText(self, -1, "1-sigma positive fraction")
608
609        self.time_ctl = OutputTextCtrl(self, -1, size=(60, 20))
610        hint_msg = "Computation time for the last inversion, in seconds."
611        self.time_ctl.SetToolTipString(hint_msg)
612
613        self.chi2_ctl = OutputTextCtrl(self, -1, size=(60, 20))
614        self.chi2_ctl.SetToolTipString("Chi^2 over degrees of freedom.")
615
616        # Oscillation parameter
617        self.osc_ctl = OutputTextCtrl(self, -1, size=(60, 20))
618        hint_msg = "Oscillation parameter. P(r) for a sphere has an "
619        hint_msg += " oscillation parameter of 1.1."
620        self.osc_ctl.SetToolTipString(hint_msg)
621
622        # Positive fraction figure of merit
623        self.pos_ctl = OutputTextCtrl(self, -1, size=(60, 20))
624        hint_msg = "Fraction of P(r) that is positive. "
625        hint_msg += "Theoretically, P(r) is defined positive."
626        self.pos_ctl.SetToolTipString(hint_msg)
627
628        # 1-simga positive fraction figure of merit
629        self.pos_err_ctl = OutputTextCtrl(self, -1, size=(60, 20))
630        message = "Fraction of P(r) that is at least 1 standard deviation"
631        message += " greater than zero.\n"
632        message += "This figure of merit tells you about the size of the "
633        message += "P(r) errors.\n"
634        message += "If it is close to 1 and the other figures of merit are bad,"
635        message += " consider changing the maximum distance."
636        self.pos_err_ctl.SetToolTipString(message)
637
638        sizer_res = wx.GridBagSizer(5, 5)
639
640        iy = 0
641        sizer_res.Add(label_rg, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
642        sizer_res.Add(self.rg_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
643        sizer_res.Add(label_rg_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
644        iy += 1
645        sizer_res.Add(label_iq0, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
646        sizer_res.Add(self.iq0_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
647        sizer_res.Add(label_iq0_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
648        iy += 1
649        sizer_res.Add(label_bck, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
650        sizer_res.Add(self.bck_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
651        sizer_res.Add(label_bck_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
652        iy += 1
653        sizer_res.Add(label_time, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
654        sizer_res.Add(self.time_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
655        sizer_res.Add(label_time_unit, (iy, 2), (1, 1), wx.RIGHT | wx.EXPAND, 15)
656        iy += 1
657        sizer_res.Add(label_chi2, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
658        sizer_res.Add(self.chi2_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
659        iy += 1
660        sizer_res.Add(label_osc, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
661        sizer_res.Add(self.osc_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
662
663        iy += 1
664        sizer_res.Add(label_pos, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
665        sizer_res.Add(self.pos_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
666
667        iy += 1
668        sizer_res.Add(label_pos_err, (iy, 0), (1, 1), wx.LEFT | wx.EXPAND, 15)
669        sizer_res.Add(self.pos_err_ctl, (iy, 1), (1, 1), wx.RIGHT | wx.EXPAND, 15)
670
671        ressizer.Add(sizer_res, 0)
672        iy_vb += 1
673        vbox.Add(ressizer, (iy_vb, 0), (1, 1),
674                 wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
675
676        # ----- Buttons -----
677        wx_id = wx.NewId()
678        button_ok = wx.Button(self, wx_id, "Compute")
679        button_ok.SetToolTipString("Perform P(r) inversion.")
680        self.Bind(wx.EVT_BUTTON, self._on_invert, id=wx_id)
681
682        self.button_help = wx.Button(self, -1, "HELP")
683        self.button_help.SetToolTipString("Get help on P(r) inversion.")
684        self.button_help.Bind(wx.EVT_BUTTON, self.on_help)
685
686        self._set_reset_flag(True)
687        self._set_save_flag(True)
688        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
689        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
690        sizer_button.Add(button_ok, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
691        sizer_button.Add(self.button_help, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
692
693        iy_vb += 1
694        vbox.Add(sizer_button, (iy_vb, 0), (1, 1),
695                 wx.EXPAND | wx.BOTTOM | wx.TOP | wx.RIGHT, 10)
696
697        self.Bind(wx.EVT_TEXT_ENTER, self._on_invert)
698
699        self.SetSizer(vbox)
700
701    def _on_accept_alpha(self, evt):
702        """
703        User has accepted the estimated alpha,
704        set it as part of the input parameters
705        """
706        try:
707            alpha = self.alpha_estimate_ctl.GetLabel()
708            # Check that we have a number
709            float(alpha)
710            self.alpha_ctl.SetValue(alpha)
711        except ValueError:
712            logging.error("InversionControl._on_accept_alpha got a value that was not a number: %s" % alpha )
713        except:
714            # No estimate or bad estimate, either do nothing
715            logging.error("InversionControl._on_accept_alpha: %s" % sys.exc_value)
716
717    def _on_accept_nterms(self, evt):
718        """
719        User has accepted the estimated number of terms,
720        set it as part of the input parameters
721        """
722        try:
723            nterms = self.nterms_estimate_ctl.GetLabel()
724            # Check that we have a number
725            float(nterms)
726            self.nfunc_ctl.SetValue(nterms)
727        except ValueError:
728            logging.error("InversionControl._on_accept_nterms got a value that was not a number: %s" % nterms )
729        except:
730            # No estimate or bad estimate, either do nothing
731            logging.error("InversionControl._on_accept_nterms: %s" % sys.exc_value)
732
733    def clear_panel(self):
734        """
735        """
736        self.plot_data.SetValue("")
737        self.on_reset(event=None)
738
739    def on_reset(self, event=None):
740        """
741        Resets inversion parameters
742        """
743        self.nfunc = self._manager.DEFAULT_NFUNC
744        self.d_max = self._manager.DEFAULT_DMAX
745        self.alpha = self._manager.DEFAULT_ALPHA
746        self.qmin_ctl.SetValue("")
747        self.qmax_ctl.SetValue("")
748        self.time_ctl.SetValue("")
749        self.rg_ctl.SetValue("")
750        self.iq0_ctl.SetValue("")
751        self.bck_ctl.SetValue("")
752        self.chi2_ctl.SetValue("")
753        self.osc_ctl.SetValue("")
754        self.pos_ctl.SetValue("")
755        self.pos_err_ctl.SetValue("")
756        self.alpha_estimate_ctl.Enable(False)
757        self.alpha_estimate_ctl.SetLabel("")
758        self.nterms_estimate_ctl.Enable(False)
759        self.nterms_estimate_ctl.SetLabel("")
760        self._set_analysis(False)
761
762        self._on_pars_changed()
763
764    def _on_pars_changed(self, evt=None):
765        """
766        Called when an input parameter has changed
767        We will estimate the alpha parameter behind the
768        scenes.
769        """
770        flag, alpha, dmax, nfunc, qmin, qmax, height, width = self._read_pars()
771        has_bck = self.bck_chk.IsChecked()
772
773        # If the pars are valid, estimate alpha
774        if flag:
775            self.nterms_estimate_ctl.Enable(False)
776            self.alpha_estimate_ctl.Enable(False)
777
778            dataset = self.plot_data.GetValue()
779            if dataset is not None and dataset.strip() != "":
780                self._manager.estimate_plot_inversion(alpha=alpha, nfunc=nfunc,
781                                                      d_max=dmax,
782                                                      q_min=qmin, q_max=qmax,
783                                                      bck=has_bck,
784                                                      height=height,
785                                                      width=width)
786
787    def _read_pars(self, evt=None):
788        """
789        """
790        alpha = 0
791        nfunc = 5
792        dmax = 120
793        qmin = 0
794        qmax = 0
795        height = 0
796        width = 0
797        flag = True
798        # Read slit height
799        try:
800            height_str = self.sheight_ctl.GetValue()
801            if len(height_str.lstrip().rstrip()) == 0:
802                height = 0
803            else:
804                height = float(height_str)
805                self.sheight_ctl.SetBackgroundColour(wx.WHITE)
806                self.sheight_ctl.Refresh()
807        except:
808            flag = False
809            self.sheight_ctl.SetBackgroundColour("pink")
810            self.sheight_ctl.Refresh()
811
812        # Read slit width
813        try:
814            width_str = self.swidth_ctl.GetValue()
815            if len(width_str.lstrip().rstrip()) == 0:
816                width = 0
817            else:
818                width = float(width_str)
819                self.swidth_ctl.SetBackgroundColour(wx.WHITE)
820                self.swidth_ctl.Refresh()
821        except:
822            flag = False
823            self.swidth_ctl.SetBackgroundColour("pink")
824            self.swidth_ctl.Refresh()
825
826        # Read alpha
827        try:
828            alpha = float(self.alpha_ctl.GetValue())
829            self.alpha_ctl.SetBackgroundColour(wx.WHITE)
830            self.alpha_ctl.Refresh()
831        except:
832            flag = False
833            self.alpha_ctl.SetBackgroundColour("pink")
834            self.alpha_ctl.Refresh()
835
836        # Read d_max
837        try:
838            dmax = float(self.dmax_ctl.GetValue())
839            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
840            self.dmax_ctl.Refresh()
841        except:
842            flag = False
843            self.dmax_ctl.SetBackgroundColour("pink")
844            self.dmax_ctl.Refresh()
845
846        # Read nfunc
847        try:
848            nfunc = int(self.nfunc_ctl.GetValue())
849            npts = self._manager.get_npts()
850            if npts > 0 and nfunc > npts:
851                message = "Number of function terms should be smaller "
852                message += "than the number of points"
853                wx.PostEvent(self._manager.parent, StatusEvent(status=message))
854                raise ValueError, message
855            self.nfunc_ctl.SetBackgroundColour(wx.WHITE)
856            self.nfunc_ctl.Refresh()
857        except:
858            flag = False
859            self.nfunc_ctl.SetBackgroundColour("pink")
860            self.nfunc_ctl.Refresh()
861
862        # Read qmin
863        try:
864            qmin_str = self.qmin_ctl.GetValue()
865            if len(qmin_str.lstrip().rstrip()) == 0:
866                qmin = None
867            else:
868                qmin = float(qmin_str)
869                self.qmin_ctl.SetBackgroundColour(wx.WHITE)
870                self.qmin_ctl.Refresh()
871        except:
872            flag = False
873            self.qmin_ctl.SetBackgroundColour("pink")
874            self.qmin_ctl.Refresh()
875
876        # Read qmax
877        try:
878            qmax_str = self.qmax_ctl.GetValue()
879            if len(qmax_str.lstrip().rstrip()) == 0:
880                qmax = None
881            else:
882                qmax = float(qmax_str)
883                self.qmax_ctl.SetBackgroundColour(wx.WHITE)
884                self.qmax_ctl.Refresh()
885        except:
886            flag = False
887            self.qmax_ctl.SetBackgroundColour("pink")
888            self.qmax_ctl.Refresh()
889
890        return flag, alpha, dmax, nfunc, qmin, qmax, height, width
891
892    def _on_explore(self, evt):
893        """
894        Invoke the d_max exploration dialog
895        """
896        from explore_dialog import ExploreDialog
897        if self._manager._last_pr is not None:
898            pr = self._manager._create_plot_pr()
899            dialog = ExploreDialog(pr, 10, None, -1, "")
900            dialog.Show()
901        else:
902            message = "No data to analyze. Please load a data set to proceed."
903            wx.PostEvent(self._manager.parent, StatusEvent(status=message))
904
905    def _on_invert(self, evt):
906        """
907        Perform inversion
908
909        :param silent: when True, there will be no output for the user
910
911        """
912        # Get the data from the form
913        # Push it to the manager
914
915        flag, alpha, dmax, nfunc, qmin, qmax, height, width = self._read_pars()
916        has_bck = self.bck_chk.IsChecked()
917
918        if flag:
919            dataset = self.plot_data.GetValue()
920            if dataset == None or len(dataset.strip()) == 0:
921                message = "No data to invert. Select a data set before"
922                message += " proceeding with P(r) inversion."
923                wx.PostEvent(self._manager.parent, StatusEvent(status=message))
924            else:
925                self._manager.setup_plot_inversion(alpha=alpha, nfunc=nfunc,
926                                                   d_max=dmax,
927                                                   q_min=qmin, q_max=qmax,
928                                                   bck=has_bck,
929                                                   height=height,
930                                                   width=width)
931        else:
932            message = "The P(r) form contains invalid values: "
933            message += "please submit it again."
934            wx.PostEvent(self.parent, StatusEvent(status=message))
935
936    def _change_file(self, evt=None, filepath=None, data=None):
937        """
938        Choose a new input file for I(q)
939        """
940        if not self._manager is None:
941            self.plot_data.SetValue(str(data.name))
942            try:
943                self._manager.show_data(data=data, reset=True)
944                self._on_pars_changed(None)
945                self._on_invert(None)
946                self._set_analysis(True)
947            except:
948                msg = "InversionControl._change_file: %s" % sys.exc_value
949                logging.error(msg)
950
951    def on_help(self, event):
952        """
953        Bring up the P(r) Documentation whenever
954        the HELP button is clicked.
955
956        Calls DocumentationWindow with the path of the location within the
957        documentation tree (after /doc/ ....".  Note that when using old
958        versions of Wx (before 2.9) and thus not the release version of
959        installers, the help comes up at the top level of the file as
960        webbrowser does not pass anything past the # to the browser when it is
961        running "file:///...."
962
963    :param evt: Triggers on clicking the help button
964    """
965
966        _TreeLocation = "user/sasgui/perspectives/pr/pr_help.html"
967        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
968                                          "P(r) Help")
969
970
971class PrDistDialog(wx.Dialog):
972    """
973    Property dialog to let the user change the number
974    of points on the P(r) plot.
975    """
976    def __init__(self, parent, id):
977        from sas.sascalc.pr.invertor import help
978        wx.Dialog.__init__(self, parent, id, size=(250, 120))
979        self.SetTitle("P(r) distribution")
980
981
982        vbox = wx.BoxSizer(wx.VERTICAL)
983
984        label_npts = wx.StaticText(self, -1, "Number of points")
985        self.npts_ctl = PrTextCtrl(self, -1, size=(100, 20))
986
987        pars_sizer = wx.GridBagSizer(5, 5)
988        iy = 0
989        pars_sizer.Add(label_npts, (iy, 0), (1, 1), wx.LEFT, 15)
990        pars_sizer.Add(self.npts_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
991
992        vbox.Add(pars_sizer, 0, wx.ALL | wx.EXPAND, 15)
993
994        static_line = wx.StaticLine(self, -1)
995        vbox.Add(static_line, 0, wx.EXPAND, 0)
996
997        button_ok = wx.Button(self, wx.ID_OK, "OK")
998        self.Bind(wx.EVT_BUTTON, self._checkValues, button_ok)
999        button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
1000
1001        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
1002        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
1003        sizer_button.Add(button_ok, 0, wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
1004        sizer_button.Add(button_cancel, 0,
1005                         wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
1006        vbox.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
1007
1008        self.SetSizer(vbox)
1009        self.SetAutoLayout(True)
1010
1011        self.Layout()
1012        self.Centre()
1013
1014    def _checkValues(self, event):
1015        """
1016        Check the dialog content.
1017        """
1018        flag = True
1019        try:
1020            int(self.npts_ctl.GetValue())
1021            self.npts_ctl.SetBackgroundColour(wx.WHITE)
1022            self.npts_ctl.Refresh()
1023        except:
1024            flag = False
1025            self.npts_ctl.SetBackgroundColour("pink")
1026            self.npts_ctl.Refresh()
1027        if flag:
1028            event.Skip(True)
1029
1030    def get_content(self):
1031        """
1032        Return the content of the dialog.
1033        At this point the values have already been
1034        checked.
1035        """
1036        value = int(self.npts_ctl.GetValue())
1037        return value
1038
1039    def set_content(self, npts):
1040        """
1041        Initialize the content of the dialog.
1042        """
1043        self.npts_ctl.SetValue("%i" % npts)
Note: See TracBrowser for help on using the repository browser.