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

Last change on this file since 332c10d was 5251ec6, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

improved support for py37 in sasgui

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