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

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 868846b was cb62bd5, checked in by lewis, 7 years ago

Use manually inputted background level in Pr calculation

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