source: sasview/src/sas/perspectives/pr/explore_dialog.py @ df9492d

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 df9492d was df9492d, checked in by Doucet, Mathieu <doucetm@…>, 10 years ago

Make sure the drop down acts like a drop down…

  • Property mode set to 100644
File size: 15.0 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 logging
23import sys
24
25# Avoid Matplotlib complaining about the lack of legend on the plot
26import warnings
27warnings.simplefilter("ignore")
28
29# Import plotting classes
30from sas.plottools.PlotPanel import PlotPanel
31from sas.plottools import Data1D as Model1D
32from sas.guiframe.gui_style import GUIFRAME_ID
33from sas.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        self.plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
73
74        # Graph       
75        self.graph = Graph()
76        self.graph.xaxis("\\rm{D_{max}}", 'A')
77        self.graph.yaxis("\\rm{%s}" % DEFAULT_OUTPUT, "")
78        self.graph.add(self.plot)
79        self.graph.render(self)
80
81        self.toolbar.DeleteToolByPos(0)
82        self.toolbar.DeleteToolByPos(8)
83        self.toolbar.Realize()
84
85    def onContextMenu(self, event):
86        """
87        Default context menu for the plot panel
88
89        :TODO: Would be nice to add printing and log/linear scales.
90            The current verison of plottools no longer plays well with
91            plots outside of guiframe. Guiframe team needs to fix this.
92        """
93        # Slicer plot popup menu
94        wx_id = wx.NewId()
95        slicerpop = wx.Menu()
96        slicerpop.Append(wx_id, '&Save image', 'Save image as PNG')
97        wx.EVT_MENU(self, wx_id, self.onSaveImage)
98
99        wx_id = wx.NewId()
100        slicerpop.AppendSeparator()
101        slicerpop.Append(wx_id, '&Reset Graph')
102        wx.EVT_MENU(self, wx_id, self.onResetGraph)
103
104        pos = event.GetPosition()
105        pos = self.ScreenToClient(pos)
106        self.PopupMenu(slicerpop, pos)
107
108class Results(object):
109    """
110    Class to hold the inversion output parameters
111    as a function of D_max
112    """
113    def __init__(self):
114        """
115        Initialization. Create empty arrays
116        and dictionary of labels.
117        """
118        # Array of output for each inversion
119        self.chi2 = []
120        self.osc = []
121        self.pos = []
122        self.pos_err = []
123        self.rg = []
124        self.iq0 = []
125        self.bck = []
126        self.d_max = []
127
128        # Dictionary of outputs
129        self.outputs = {}
130        self.outputs['Chi2/dof'] = ["\chi^2/dof", "a.u.", self.chi2]
131        self.outputs['Oscillation parameter'] = ["Osc", "a.u.", self.osc]
132        self.outputs['Positive fraction'] = ["P^+", "a.u.", self.pos]
133        self.outputs['1-sigma positive fraction'] = ["P^+_{1\ \sigma}",
134                                                     "a.u.", self.pos_err]
135        self.outputs['Rg'] = ["R_g", "A", self.rg]
136        self.outputs['I(q=0)'] = ["I(q=0)", "1/A", self.iq0]
137        self.outputs['Background'] = ["Bck", "1/A", self.bck]
138
139class ExploreDialog(wx.Dialog):
140    """
141    The explorer dialog box. This dialog is meant to be
142    invoked by the InversionControl class.
143    """
144
145    def __init__(self, pr_state, nfunc, *args, **kwds):
146        """
147        Initialization. The parameters added to Dialog are:
148
149        :param pr_state: sas.pr.invertor.Invertor object
150        :param nfunc: Number of terms in the expansion
151
152        """
153        kwds["style"] = wx.RESIZE_BORDER | wx.DEFAULT_DIALOG_STYLE
154        wx.Dialog.__init__(self, *args, **kwds)
155
156        # Initialize Results object
157        self.results = Results()
158
159        self.pr_state = pr_state
160        self._default_min = 0.9 * self.pr_state.d_max
161        self._default_max = 1.1 * self.pr_state.d_max
162        self.nfunc = nfunc
163
164        # Control for number of points
165        self.npts_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
166                                   size=(60, 20))
167        # Control for the minimum value of D_max
168        self.dmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
169                                   size=(60, 20))
170        # Control for the maximum value of D_max
171        self.dmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
172                                   size=(60, 20))
173
174        # Output selection box for the y axis
175        self.output_box = None
176
177        # Create the plot object
178        self.plotpanel = OutputPlot(self._default_min, self._default_max,
179                                    self, -1, style=wx.RAISED_BORDER)
180
181        # Create the layout of the dialog
182        self.__do_layout()
183        self.Fit()
184
185        # Calculate exploration results
186        self._recalc()
187        # Graph the default output curve
188        self._plot_output()
189
190    class Event(object):
191        """
192        Class that holds the content of the form
193        """
194        ## Number of points to be plotted
195        npts = 0
196        ## Minimum value of D_max
197        dmin = 0
198        ## Maximum value of D_max
199        dmax = 0
200
201    def _get_values(self, event=None):
202        """
203        Invoked when the user changes a value of the form.
204        Check that the values are of the right type.
205
206        :return: ExploreDialog.Event object if the content is good,
207            None otherwise
208        """
209        # Flag to make sure that all values are good
210        flag = True
211
212        # Empty ExploreDialog.Event content
213        content_event = self.Event()
214
215        # Read each text control and make sure the type is valid
216        # Let the user know if a type is invalid by changing the
217        # background color of the control.
218        try:
219            content_event.npts = int(self.npts_ctl.GetValue())
220            self.npts_ctl.SetBackgroundColour(wx.WHITE)
221            self.npts_ctl.Refresh()
222        except:
223            flag = False
224            self.npts_ctl.SetBackgroundColour("pink")
225            self.npts_ctl.Refresh()
226
227        try:
228            content_event.dmin = float(self.dmin_ctl.GetValue())
229            self.dmin_ctl.SetBackgroundColour(wx.WHITE)
230            self.dmin_ctl.Refresh()
231        except:
232            flag = False
233            self.dmin_ctl.SetBackgroundColour("pink")
234            self.dmin_ctl.Refresh()
235
236        try:
237            content_event.dmax = float(self.dmax_ctl.GetValue())
238            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
239            self.dmax_ctl.Refresh()
240        except:
241            flag = False
242            self.dmax_ctl.SetBackgroundColour("pink")
243            self.dmax_ctl.Refresh()
244
245        # If the content of the form is valid, return the content,
246        # otherwise return None
247        if flag:
248            if event is not None:
249                event.Skip(True)
250            return content_event
251        else:
252            return None
253
254    def _plot_output(self, event=None):
255        """
256        Invoked when a new output type is selected for plotting,
257        or when a new computation is finished.
258        """
259        # Get the output type selection
260        output_type = self.output_box.GetString(self.output_box.GetSelection())
261
262        # If the selected output type is part of the results ojbect,
263        # display the results.
264        # Note: by design, the output type should always be part of the
265        #       results object.
266        if self.results.outputs.has_key(output_type):
267            self.plotpanel.plot.x = self.results.d_max
268            self.plotpanel.plot.y = self.results.outputs[output_type][2]
269            self.plotpanel.plot.name = '_nolegend_'
270            y_label = "\\rm{%s}" % self.results.outputs[output_type][0]
271            self.plotpanel.graph.yaxis(y_label,
272                                       self.results.outputs[output_type][1])
273
274            # Redraw
275            self.plotpanel.graph.render(self.plotpanel)
276            self.plotpanel.subplot.figure.canvas.draw_idle()
277        else:
278            msg = "ExploreDialog: the Results object's dictionary "
279            msg += "does not contain "
280            msg += "the [%s] output type. This must be indicative of "
281            msg += "a change in the " % str(output_type)
282            msg += "ExploreDialog code."
283            logging.error(msg)
284
285    def __do_layout(self):
286        """
287        Do the layout of the dialog
288        """
289        # Dialog box properties
290        self.SetTitle("D_max Explorer")
291        self.SetSize((600, 595))
292
293        sizer_main = wx.BoxSizer(wx.VERTICAL)
294        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
295        sizer_params = wx.GridBagSizer(5, 5)
296
297        iy = 0
298        ix = 0
299        label_npts = wx.StaticText(self, -1, "Npts")
300        sizer_params.Add(label_npts, (iy, ix), (1, 1),
301                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
302        ix += 1
303        sizer_params.Add(self.npts_ctl, (iy, ix), (1, 1),
304                         wx.EXPAND | wx.ADJUST_MINSIZE, 0)
305        self.npts_ctl.SetValue("%g" % DEFAULT_NPTS)
306        self.npts_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
307
308        ix += 1
309        label_dmin = wx.StaticText(self, -1, "Min Distance [A]")
310        sizer_params.Add(label_dmin, (iy, ix), (1, 1),
311                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
312        ix += 1
313        sizer_params.Add(self.dmin_ctl, (iy, ix), (1, 1),
314                         wx.EXPAND | wx.ADJUST_MINSIZE, 0)
315        self.dmin_ctl.SetValue(str(self._default_min))
316        self.dmin_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
317
318        ix += 1
319        label_dmax = wx.StaticText(self, -1, "Max Distance [A]")
320        sizer_params.Add(label_dmax, (iy, ix), (1, 1),
321                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
322        ix += 1
323        sizer_params.Add(self.dmax_ctl, (iy, ix), (1, 1),
324                         wx.EXPAND | wx.ADJUST_MINSIZE, 0)
325        self.dmax_ctl.SetValue(str(self._default_max))
326        self.dmax_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
327
328
329        # Ouput selection box
330        selection_msg = wx.StaticText(self, -1, "Select a dependent variable:")
331        self.output_box = wx.ComboBox(self, -1, style=wx.CB_READONLY)
332        for item in self.results.outputs.keys():
333            self.output_box.Append(item, "")
334        self.output_box.SetStringSelection(DEFAULT_OUTPUT)
335        self.output_box.SetEditable(False)
336
337        output_sizer = wx.GridBagSizer(5, 5)
338        output_sizer.Add(selection_msg, (0, 0), (1, 1),
339                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
340        output_sizer.Add(self.output_box, (0, 1), (1, 2),
341                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
342
343        self.Bind(wx.EVT_COMBOBOX, self._plot_output)
344        #wx.EVT_COMBOBOX(self.output_box, -1, self._plot_output)
345        sizer_main.Add(output_sizer, 0, wx.EXPAND | wx.ALL, 10)
346
347        sizer_main.Add(self.plotpanel, 0, wx.EXPAND | wx.ALL, 10)
348        sizer_main.SetItemMinSize(self.plotpanel, 400, 400)
349
350        sizer_main.Add(sizer_params, 0, wx.EXPAND | wx.ALL, 10)
351        static_line_3 = wx.StaticLine(self, -1)
352        sizer_main.Add(static_line_3, 0, wx.EXPAND, 0)
353
354        # Bottom area with the close button
355        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
356        button_OK = wx.Button(self, wx.ID_OK, "Close")
357        sizer_button.Add(button_OK, 0, wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
358
359        sizer_main.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
360        self.SetAutoLayout(True)
361        self.SetSizer(sizer_main)
362        self.Layout()
363        self.Centre()
364
365        # Bind the Enter key to recalculation
366        self.Bind(wx.EVT_TEXT_ENTER, self._recalc)
367
368    def set_plot_unfocus(self):
369        """
370        Not implemented
371        """
372        pass
373
374    def _recalc(self, event=None):
375        """
376        Invoked when the user changed a value on the form.
377        Process the form and compute the output to be plottted.
378        """
379        # Get the content of the form
380        content = self._get_values()
381        # If the content of the form is invalid, return and do nothing
382        if content is None:
383            return
384
385        # Results object to store the computation outputs.
386        results = Results()
387
388        # Loop over d_max values
389        for i in range(content.npts):
390            temp = (content.dmax - content.dmin) / (content.npts - 1.0)
391            d = content.dmin + i * temp
392
393            self.pr_state.d_max = d
394            try:
395                out, cov = self.pr_state.invert(self.nfunc)
396
397                # Store results
398                iq0 = self.pr_state.iq0(out)
399                rg = self.pr_state.rg(out)
400                pos = self.pr_state.get_positive(out)
401                pos_err = self.pr_state.get_pos_err(out, cov)
402                osc = self.pr_state.oscillations(out)
403
404                results.d_max.append(self.pr_state.d_max)
405                results.bck.append(self.pr_state.background)
406                results.chi2.append(self.pr_state.chi2)
407                results.iq0.append(iq0)
408                results.rg.append(rg)
409                results.pos.append(pos)
410                results.pos_err.append(pos_err)
411                results.osc.append(osc)
412            except:
413                # This inversion failed, skip this D_max value
414                msg = "ExploreDialog: inversion failed "
415                msg += "for D_max=%s\n%s" % (str(d), sys.exc_value)
416                logging.error(msg)
417
418        self.results = results
419
420        # Plot the selected output
421        self._plot_output()
Note: See TracBrowser for help on using the repository browser.