source: sasview/prview/src/sans/perspectives/pr/explore_dialog.py @ 24e093a

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 24e093a was 24e093a, checked in by Jessica Tumarkin <jtumarki@…>, 13 years ago
  • Property mode set to 100644
File size: 16.5 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#See the license text in license.txt
8#
9#copyright 2009, University of Tennessee
10################################################################################
11
12"""
13Dialog panel to explore the P(r) inversion results for a range
14of D_max value. User picks a number of points and a range of
15distances, then can toggle between inversion outputs and see
16their distribution as a function of D_max.
17"""
18   
19
20import wx
21import numpy
22import math
23import logging
24import sys
25
26# Avoid Matplotlib complaining about the lack of legend on the plot
27import warnings
28warnings.simplefilter("ignore")
29
30# Import plotting classes
31from danse.common.plottools.PlotPanel import PlotPanel
32from danse.common.plottools import Theory1D as Model1D
33from danse.common.plottools.plottables import Graph
34
35from pr_widgets import PrTextCtrl
36
37# Default number of points on the output plot
38DEFAULT_NPTS = 10
39# Default output parameter to plot
40DEFAULT_OUTPUT = 'Chi2/dof'
41
42class OutputPlot(PlotPanel):
43    """
44    Plot panel used to show the selected results as a function
45    of D_max
46    """
47    def __init__(self, d_min, d_max, parent, id = -1, color = None,\
48        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
49        """
50        Initialization. The parameters added to PlotPanel are:
51       
52        :param d_min: Minimum value of D_max to explore
53        :param d_max: Maximum value of D_max to explore
54       
55        """
56        PlotPanel.__init__(self, parent, id = id, style = style, **kwargs)
57
58        self.parent = parent
59        self.min = d_min
60        self.max = d_max
61        self.npts = DEFAULT_NPTS
62
63        step = (self.max-self.min)/(self.npts-1)
64        self.x = numpy.arange(self.min, self.max+step*0.01, step)
65        dx = numpy.zeros(len(self.x))
66        y = numpy.ones(len(self.x))
67        dy = numpy.zeros(len(self.x))
68 
69        # Plot area
70        self.plot = Model1D(self.x, y=y, dy=dy)
71        self.plot.name = DEFAULT_OUTPUT
72
73        # Graph       
74        self.graph = Graph()
75        self.graph.xaxis("\\rm{D_{max}}", 'A')
76        self.graph.yaxis("\\rm{%s}" % DEFAULT_OUTPUT,"")
77        self.graph.add(self.plot)
78        self.graph.render(self)
79
80    def onContextMenu(self, event):
81        """
82        Default context menu for the plot panel
83       
84        :TODO: Would be nice to add printing and log/linear scales.
85            The current verison of plottools no longer plays well with
86            plots outside of guiframe. Guiframe team needs to fix this.
87        """
88        # Slicer plot popup menu
89        id = wx.NewId()
90        slicerpop = wx.Menu()
91        slicerpop.Append(id, '&Save image', 'Save image as PNG')
92        wx.EVT_MENU(self, id, self.onSaveImage)
93       
94        id = wx.NewId()
95        slicerpop.AppendSeparator()
96        slicerpop.Append(id, '&Reset Graph')
97        wx.EVT_MENU(self, id, self.onResetGraph)
98       
99        pos = event.GetPosition()
100        pos = self.ScreenToClient(pos)
101        self.PopupMenu(slicerpop, pos)
102
103class Results:
104    """
105    Class to hold the inversion output parameters
106    as a function of D_max
107    """
108    def __init__(self):
109        """
110        Initialization. Create empty arrays
111        and dictionary of labels.
112        """
113        # Array of output for each inversion
114        self.chi2 = []
115        self.osc = []
116        self.pos = []
117        self.pos_err = []
118        self.rg = []
119        self.iq0 = []
120        self.bck = []
121        self.d_max = []
122       
123        # Dictionary of outputs
124        self.outputs = {}
125        self.outputs['Chi2/dof'] = ["\chi^2/dof", "a.u.", self.chi2]   
126        self.outputs['Oscillation parameter'] = ["Osc", "a.u.", self.osc]   
127        self.outputs['Positive fraction'] = ["P^+", "a.u.", self.pos]   
128        self.outputs['1-sigma positive fraction'] = ["P^+_{1\ \sigma}", 
129                                                     "a.u.", self.pos_err]   
130        self.outputs['Rg'] = ["R_g", "A", self.rg]   
131        self.outputs['I(q=0)'] = ["I(q=0)", "1/A", self.iq0]   
132        self.outputs['Background'] = ["Bck", "1/A", self.bck] 
133       
134class ExploreDialog(wx.Dialog):
135    """
136    The explorer dialog box. This dialog is meant to be
137    invoked by the InversionControl class.
138    """
139   
140    def __init__(self, pr_state, nfunc, *args, **kwds):
141        """
142        Initialization. The parameters added to Dialog are:
143       
144        :param pr_state: sans.pr.invertor.Invertor object
145        :param nfunc: Number of terms in the expansion
146       
147        """
148        kwds["style"] = wx.RESIZE_BORDER|wx.DEFAULT_DIALOG_STYLE
149        wx.Dialog.__init__(self, *args, **kwds)
150       
151        # Initialize Results object
152        self.results = Results()
153       
154        self.pr_state  = pr_state
155        self._default_min = 0.9*self.pr_state.d_max
156        self._default_max = 1.1*self.pr_state.d_max
157        self.nfunc     = nfunc
158       
159        # Control for number of points
160        self.npts_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
161                                   size=(60,20))
162        # Control for the minimum value of D_max
163        self.dmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
164                                   size=(60,20))
165        # Control for the maximum value of D_max
166        self.dmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
167                                   size=(60,20))
168
169        # Output selection box for the y axis
170        self.output_box = None
171
172        # Create the plot object
173        self.plotpanel = OutputPlot(self._default_min, self._default_max,
174                                    self, -1, style=wx.RAISED_BORDER)
175       
176        # Create the layout of the dialog
177        self.__do_layout()
178        self.Fit()
179       
180        # Calculate exploration results
181        self._recalc()
182        # Graph the default output curve
183        self._plot_output()
184       
185    class Event:
186        """
187        Class that holds the content of the form
188        """
189        ## Number of points to be plotted
190        npts   = 0
191        ## Minimum value of D_max
192        dmin   = 0
193        ## Maximum value of D_max
194        dmax   = 0
195               
196    def _get_values(self, event=None):
197        """
198        Invoked when the user changes a value of the form.
199        Check that the values are of the right type.
200       
201        :return: ExploreDialog.Event object if the content is good,
202            None otherwise
203        """
204        # Flag to make sure that all values are good
205        flag = True
206       
207        # Empty ExploreDialog.Event content
208        content_event = self.Event()
209       
210        # Read each text control and make sure the type is valid
211        # Let the user know if a type is invalid by changing the
212        # background color of the control.
213        try:
214            content_event.npts = int(self.npts_ctl.GetValue())
215            self.npts_ctl.SetBackgroundColour(wx.WHITE)
216            self.npts_ctl.Refresh()
217        except:
218            flag = False
219            self.npts_ctl.SetBackgroundColour("pink")
220            self.npts_ctl.Refresh()
221           
222        try:
223            content_event.dmin = float(self.dmin_ctl.GetValue())
224            self.dmin_ctl.SetBackgroundColour(wx.WHITE)
225            self.dmin_ctl.Refresh()
226        except:
227            flag = False
228            self.dmin_ctl.SetBackgroundColour("pink")
229            self.dmin_ctl.Refresh()
230       
231        try:
232            content_event.dmax = float(self.dmax_ctl.GetValue())
233            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
234            self.dmax_ctl.Refresh()
235        except:
236            flag = False
237            self.dmax_ctl.SetBackgroundColour("pink")
238            self.dmax_ctl.Refresh()
239       
240        # If the content of the form is valid, return the content,
241        # otherwise return None
242        if flag:
243            if event is not None:
244                event.Skip(True)
245            return content_event
246        else:
247            return None
248   
249    def _plot_output(self, event=None):
250        """
251        Invoked when a new output type is selected for plotting,
252        or when a new computation is finished.
253        """
254        # Get the output type selection
255        output_type = self.output_box.GetString(self.output_box.GetSelection())
256       
257        # If the selected output type is part of the results ojbect,
258        # display the results.
259        # Note: by design, the output type should always be part of the
260        #       results object.
261        if self.results.outputs.has_key(output_type): 
262            self.plotpanel.plot.x = self.results.d_max
263            self.plotpanel.plot.y = self.results.outputs[output_type][2]
264            self.plotpanel.plot.name = '_nolegend_'
265            y_label = "\\rm{%s}" % self.results.outputs[output_type][0]
266            self.plotpanel.graph.yaxis(y_label,
267                                       self.results.outputs[output_type][1])
268           
269            # Redraw
270            self.plotpanel.graph.render(self.plotpanel)
271            self.plotpanel.subplot.figure.canvas.draw_idle()
272        else:
273            msg =  "ExploreDialog: the Results object's dictionary "
274            msg += "does not contain "
275            msg += "the [%s] output type. This must be indicative of "
276            msg += "a change in the " % str(output_type)
277            msg += "ExploreDialog code."
278            logging.error(msg)
279       
280    def __do_layout(self):
281        """
282        Do the layout of the dialog
283        """
284        # Dialog box properties
285        self.SetTitle("D_max Explorer")
286        self.SetSize((600, 595))
287       
288        sizer_main = wx.BoxSizer(wx.VERTICAL)
289        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
290        sizer_params = wx.GridBagSizer(5, 5)
291
292        iy = 0
293        ix = 0
294        label_npts  = wx.StaticText(self, -1, "Npts")
295        sizer_params.Add(label_npts, (iy, ix), (1, 1), 
296                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
297        ix += 1
298        sizer_params.Add(self.npts_ctl,   (iy, ix), (1, 1), 
299                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
300        self.npts_ctl.SetValue("%g" % DEFAULT_NPTS)
301        self.npts_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
302       
303        ix += 1
304        label_dmin   = wx.StaticText(self, -1, "Min Distance [A]")
305        sizer_params.Add(label_dmin, (iy, ix), (1, 1),
306                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
307        ix += 1
308        sizer_params.Add(self.dmin_ctl, (iy, ix), (1, 1),
309                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
310        self.dmin_ctl.SetValue(str(self._default_min))
311        self.dmin_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
312       
313        ix += 1
314        label_dmax = wx.StaticText(self, -1, "Max Distance [A]")
315        sizer_params.Add(label_dmax, (iy, ix), (1, 1),
316                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
317        ix += 1
318        sizer_params.Add(self.dmax_ctl,   (iy, ix), (1, 1),
319                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
320        self.dmax_ctl.SetValue(str(self._default_max))
321        self.dmax_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
322
323
324        # Ouput selection box
325        selection_msg = wx.StaticText(self, -1, "Select a dependent variable:")
326        self.output_box = wx.ComboBox(self, -1)
327        for item in self.results.outputs.keys():
328            self.output_box.Append(item, "")
329        self.output_box.SetStringSelection(DEFAULT_OUTPUT)
330       
331        output_sizer = wx.GridBagSizer(5, 5)
332        output_sizer.Add(selection_msg, (0, 0), (1, 1),
333                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
334        output_sizer.Add(self.output_box, (0, 1), (1, 2),
335                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
336       
337        wx.EVT_COMBOBOX(self.output_box, -1, self._plot_output) 
338        sizer_main.Add(output_sizer, 0, wx.EXPAND | wx.ALL, 10)
339       
340        sizer_main.Add(self.plotpanel, 0, wx.EXPAND | wx.ALL, 10)
341        sizer_main.SetItemMinSize(self.plotpanel, 400, 400)
342       
343        sizer_main.Add(sizer_params, 0, wx.EXPAND|wx.ALL, 10)
344        static_line_3 = wx.StaticLine(self, -1)
345        sizer_main.Add(static_line_3, 0, wx.EXPAND, 0)
346       
347        # Bottom area with the close button
348        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
349        button_OK = wx.Button(self, wx.ID_OK, "Close")
350        sizer_button.Add(button_OK, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
351       
352        sizer_main.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
353        self.SetAutoLayout(True)
354        self.SetSizer(sizer_main)
355        self.Layout()
356        self.Centre()
357       
358        # Bind the Enter key to recalculation
359        self.Bind(wx.EVT_TEXT_ENTER, self._recalc)
360
361    def _recalc(self, event=None):
362        """
363        Invoked when the user changed a value on the form.
364        Process the form and compute the output to be plottted.
365        """
366        # Get the content of the form
367        content = self._get_values()
368        # If the content of the form is invalid, return and do nothing
369        if content is None:
370            return
371       
372        # Results object to store the computation outputs.
373        results = Results()
374       
375        # Loop over d_max values
376        for i in range(content.npts): 
377            temp = (content.dmax - content.dmin)/(content.npts - 1.0)   
378            d = content.dmin + i * temp
379               
380            self.pr_state.d_max = d
381            try:
382                out, cov = self.pr_state.invert(self.nfunc)   
383           
384                # Store results
385                iq0 = self.pr_state.iq0(out)
386                rg = self.pr_state.rg(out)
387                pos = self.pr_state.get_positive(out)
388                pos_err = self.pr_state.get_pos_err(out, cov)
389                osc = self.pr_state.oscillations(out)
390           
391                results.d_max.append(self.pr_state.d_max)
392                results.bck.append(self.pr_state.background)
393                results.chi2.append(self.pr_state.chi2)
394                results.iq0.append(iq0)
395                results.rg.append(rg)
396                results.pos.append(pos)
397                results.pos_err.append(pos_err)
398                results.osc.append(osc)           
399            except:
400                # This inversion failed, skip this D_max value
401                msg = "ExploreDialog: inversion failed "
402                msg += "for D_max=%s\n%s" % (str(d), sys.exc_value)
403                logging.error(msg)
404           
405        self.results = results           
406         
407        # Plot the selected output
408        self._plot_output()   
409           
410##### testing code ############################################################
411"""
412Example: ::
413
414class MyApp(wx.App):
415   
416    #Test application used to invoke the ExploreDialog for testing
417   
418    def OnInit(self):
419        from inversion_state import Reader
420        from sans.pr.invertor import Invertor
421        wx.InitAllImageHandlers()
422       
423        def call_back(state, datainfo=None):
424           
425            #Dummy call-back method used by the P(r)
426            #file reader.
427           
428            print state
429           
430        # Load test data
431        # This would normally be loaded by the application
432        # of the P(r) plug-in.
433        r = Reader(call_back)
434        data = r.read("test_pr_data.prv")
435        pr_state = data.meta_data['prstate']
436       
437        # Create Invertor object for the data
438        # This would normally be assembled by the P(r) plug-in
439        pr = Invertor()
440        pr.d_max = pr_state.d_max
441        pr.alpha = pr_state.alpha
442        pr.q_min = pr_state.qmin
443        pr.q_max = pr_state.qmax
444        pr.x = data.x
445        pr.y = data.y
446        pr.err = data.y*0.1
447        pr.has_bck = False
448        pr.slit_height = pr_state.height
449        pr.slit_width = pr_state.width
450       
451        # Create the dialog
452        dialog = ExploreDialog(pr, 10, None, -1, "")
453        self.SetTopWindow(dialog)
454        dialog.ShowModal()
455        dialog.Destroy()
456
457        return 1
458
459if __name__ == "__main__":
460    app = MyApp(0)
461    app.MainLoop()
462   
463""" 
Note: See TracBrowser for help on using the repository browser.