source: sasview/src/sas/sasgui/perspectives/invariant/invariant.py @ 41d6187

Last change on this file since 41d6187 was fa81e94, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial commit of the P(r) inversion perspective.
Code merged from Jeff Krzywon's ESS_GUI_Pr branch.
Also, minor 2to3 mods to sascalc/sasgui to enble error free setup.

  • Property mode set to 100755
File size: 13.3 KB
Line 
1
2
3
4################################################################################
5# This software was developed by the University of Tennessee as part of the
6# Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7# project funded by the US National Science Foundation.
8#
9# See the license text in license.txt
10#
11# copyright 2009, University of Tennessee
12################################################################################
13
14
15import sys
16import wx
17import copy
18import logging
19from sas.sasgui.guiframe.gui_manager import MDIFrame
20from sas.sasgui.guiframe.dataFitting import Data1D
21from sas.sasgui.guiframe.events import NewPlotEvent
22from sas.sasgui.guiframe.events import StatusEvent
23from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
24from sas.sasgui.perspectives.invariant.invariant_state import Reader as reader
25from sas.sascalc.dataloader.loader import Loader
26from sas.sasgui.perspectives.invariant.invariant_panel import InvariantPanel
27from sas.sasgui.guiframe.plugin_base import PluginBase
28
29logger = logging.getLogger(__name__)
30
31class Plugin(PluginBase):
32    """
33    This class defines the interface for invariant Plugin class
34    that can be used by the gui_manager.
35    """
36
37    def __init__(self):
38        PluginBase.__init__(self, name="Invariant")
39
40        # dictionary containing data name and error on dy of that data
41        self.err_dy = {}
42
43        # default state objects
44        self.state_reader = None
45        self._extensions = '.inv'
46        self.temp_state = None
47        self.__data = None
48
49        # Log startup
50        logger.info("Invariant plug-in started")
51
52    def get_data(self):
53        """
54        """
55        return self.__data
56
57    def get_panels(self, parent):
58        """
59        Create and return the list of wx.Panels for your plug-in.
60        Define the plug-in perspective.
61
62        Panels should inherit from DefaultPanel defined below,
63        or should present the same interface. They must define
64        "window_caption" and "window_name".
65
66        :param parent: parent window
67
68        :return: list of panels
69        """
70        # # Save a reference to the parent
71        self.parent = parent
72        self.frame = MDIFrame(self.parent, None, 'None', (100, 200))
73        self.invariant_panel = InvariantPanel(parent=self.frame)
74        self.frame.set_panel(self.invariant_panel)
75        self._frame_set_helper()
76        self.invariant_panel.set_manager(manager=self)
77        self.perspective.append(self.invariant_panel.window_name)
78        # Create reader when fitting panel are created
79        self.state_reader = reader(self.set_state)
80        # append that reader to list of available reader
81        loader = Loader()
82        loader.associate_file_reader(".inv", self.state_reader)
83        # loader.associate_file_reader(".svs", self.state_reader)
84        # Return the list of panels
85        return [self.invariant_panel]
86
87    def get_context_menu(self, plotpanel=None):
88        """
89        This method is optional.
90
91        When the context menu of a plot is rendered, the
92        get_context_menu method will be called to give you a
93        chance to add a menu item to the context menu.
94
95        A ref to a Graph object is passed so that you can
96        investigate the plot content and decide whether you
97        need to add items to the context menu.
98
99        This method returns a list of menu items.
100        Each item is itself a list defining the text to
101        appear in the menu, a tool-tip help text, and a
102        call-back method.
103
104        :param graph: the Graph object to which we attach the context menu
105
106        :return: a list of menu items with call-back function
107        """
108        graph = plotpanel.graph
109        invariant_option = "Compute invariant"
110        invariant_hint = "Will displays the invariant panel for"
111        invariant_hint += " further computation"
112
113        if graph.selected_plottable not in plotpanel.plots:
114            return []
115        data = plotpanel.plots[graph.selected_plottable]
116
117        if issubclass(data.__class__, Data1D):
118            if data.name != "$I_{obs}(q)$" and  data.name != " $P_{fit}(r)$":
119                return [[invariant_option, invariant_hint, self._compute_invariant]]
120        return []
121
122    def _compute_invariant(self, event):
123        """
124        Open the invariant panel to invariant computation
125        """
126        self.panel = event.GetEventObject()
127        Plugin.on_perspective(self, event=event)
128        id = self.panel.graph.selected_plottable
129        data = self.panel.plots[self.panel.graph.selected_plottable]
130        if data is None:
131            return
132        if not issubclass(data.__class__, Data1D):
133            name = data.__class__.__name__
134            msg = "Invariant use only Data1D got: [%s] " % str(name)
135            raise ValueError(msg)
136        self.compute_helper(data=data)
137
138    def set_data(self, data_list=None):
139        """
140        receive a list of data and compute invariant
141        """
142        msg = ""
143        data = None
144        if data_list is None:
145            data_list = []
146        if len(data_list) >= 1:
147            if len(data_list) == 1:
148                data = data_list[0]
149            else:
150                data_1d_list = []
151                data_2d_list = []
152                error_msg = ""
153                # separate data into data1d and data2d list
154                for data in data_list:
155                    if data is not None:
156                        if issubclass(data.__class__, Data1D):
157                            data_1d_list.append(data)
158                        else:
159                            error_msg += " %s  type %s \n" % (str(data.name),
160                                                              str(data.__class__.__name__))
161                            data_2d_list.append(data)
162                if len(data_2d_list) > 0:
163                    msg = "Invariant does not support the following data types:\n"
164                    msg += error_msg
165                if len(data_1d_list) == 0:
166                    wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
167                    return
168                msg += "Invariant panel does not allow multiple data!\n"
169                msg += "Please select one.\n"
170                if len(data_list) > 1:
171                    from .invariant_widgets import DataDialog
172                    dlg = DataDialog(data_list=data_1d_list, text=msg)
173                    if dlg.ShowModal() == wx.ID_OK:
174                        data = dlg.get_data()
175                    else:
176                        data = None
177                    dlg.Destroy()
178
179            if data is None:
180                msg += "invariant receives no data. \n"
181                wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
182                return
183            if not issubclass(data.__class__, Data1D):
184                msg += "invariant cannot be computed for data of "
185                msg += "type %s\n" % (data.__class__.__name__)
186                wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
187                return
188            else:
189                wx.PostEvent(self.parent, NewPlotEvent(plot=data, title=data.title))
190                try:
191                    self.compute_helper(data)
192                except:
193                    msg = "Invariant Set_data: " + str(sys.exc_info()[1])
194                    wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
195        else:
196            msg = "invariant cannot be computed for data of "
197            msg += "type %s" % (data.__class__.__name__)
198            wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
199
200    def delete_data(self, data_id):
201        """
202        """
203        if self.__data is None:
204            return
205        for id in data_id:
206            if id == self.__data.id:
207                self.clear_panel()
208
209    def clear_panel(self):
210        """
211        """
212        self.invariant_panel.clear_panel()
213
214    def compute_helper(self, data):
215        """
216        """
217        if data is None:
218            return
219        # set current data if not it's a state data
220        if not self.invariant_panel.is_state_data:
221            # Store reference to data
222            self.__data = data
223            # Set the data set to be user for invariant calculation
224            self.invariant_panel.set_data(data=data)
225
226    def save_file(self, filepath, state=None):
227        """
228        Save data in provided state object.
229
230        :param filepath: path of file to write to
231        :param state: invariant state
232        """
233        # Write the state to file
234        # First, check that the data is of the right type
235        current_plottable = self.__data
236
237        if issubclass(current_plottable.__class__, Data1D):
238            self.state_reader.write(filepath, current_plottable, state)
239        else:
240            msg = "invariant.save_file: the data being saved is"
241            msg += " not a sas.sascalc.dataloader.data_info.Data1D object"
242            raise RuntimeError(msg)
243
244    def set_state(self, state=None, datainfo=None):
245        """
246        Call-back method for the state reader.
247        This method is called when a .inv/.svs file is loaded.
248
249        :param state: State object
250        """
251        self.temp_state = None
252        try:
253            if datainfo.__class__.__name__ == 'list':
254                data = datainfo[0]
255            else:
256                data = datainfo
257            if data is None:
258                msg = "invariant.set_state: datainfo parameter cannot"
259                msg += " be None in standalone mode"
260                raise RuntimeError(msg)
261            # Make sure the user sees the invariant panel after loading
262            # self.parent.set_perspective(self.perspective)
263            self.on_perspective(event=None)
264            name = data.meta_data['invstate'].file
265            data.meta_data['invstate'].file = name
266            data.name = name
267            data.filename = name
268
269            data = self.parent.create_gui_data(data, None)
270            self.__data = data
271            wx.PostEvent(self.parent, NewPlotEvent(plot=self.__data,
272                                                   reset=True, title=self.__data.title))
273            data_dict = {self.__data.id:self.__data}
274            self.parent.add_data(data_list=data_dict)
275            # set state
276            self.invariant_panel.is_state_data = True
277
278            # Load the invariant states
279            self.temp_state = state
280            # Requires to have self.__data and self.temp_state  first.
281            self.on_set_state_helper(None)
282
283        except:
284            logger.error("invariant.set_state: %s" % sys.exc_info()[1])
285
286    def on_set_state_helper(self, event=None):
287        """
288        Set the state when called by EVT_STATE_UPDATE event from guiframe
289        after a .inv/.svs file is loaded
290        """
291        self.invariant_panel.set_state(state=self.temp_state,
292                                       data=self.__data)
293        self.temp_state = None
294
295
296    def plot_theory(self, data=None, name=None):
297        """
298        Receive a data set and post a NewPlotEvent to parent.
299
300        :param data: extrapolated data to be plotted
301        :param name: Data's name to use for the legend
302        """
303        # import copy
304        if data is None:
305            id = str(self.__data.id) + name
306            group_id = self.__data.group_id
307            wx.PostEvent(self.parent, NewPlotEvent(id=id, group_id=group_id, action='Remove'))
308            return
309
310        new_plot = Data1D(x=[], y=[], dy=None)
311        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
312        scale = self.invariant_panel.get_scale()
313        background = self.invariant_panel.get_background()
314
315        if scale != 0:
316            # Put back the sacle and bkg for plotting
317            data.y = (data.y + background) / scale
318            new_plot = Data1D(x=data.x, y=data.y, dy=None)
319            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
320        else:
321            msg = "Scale can not be zero."
322            raise ValueError(msg)
323        if len(new_plot.x) == 0:
324            return
325
326        new_plot.name = name
327        new_plot.xaxis(self.__data._xaxis, self.__data._xunit)
328        new_plot.yaxis(self.__data._yaxis, self.__data._yunit)
329        new_plot.group_id = self.__data.group_id
330        new_plot.id = str(self.__data.id) + name
331        new_plot.title = self.__data.title
332        # Save theory_data in a state
333        if data is not None:
334            name_head = name.split('-')
335            if name_head[0] == 'Low':
336                self.invariant_panel.state.theory_lowQ = copy.deepcopy(new_plot)
337            elif name_head[0] == 'High':
338                self.invariant_panel.state.theory_highQ = copy.deepcopy(new_plot)
339
340        self.parent.update_theory(data_id=self.__data.id, theory=new_plot)
341        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
342                                               title=self.__data.title))
343
344    def plot_data(self, scale, background):
345        """
346        replot the current data if the user enters a new scale or background
347        """
348        new_plot = scale * self.__data - background
349        new_plot.name = self.__data.name
350        new_plot.group_id = self.__data.group_id
351        new_plot.id = self.__data.id
352        new_plot.title = self.__data.title
353
354        # Save data in a state: but seems to never happen
355        if new_plot is not None:
356            self.invariant_panel.state.data = copy.deepcopy(new_plot)
357        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
358                                               title=new_plot.title))
359
Note: See TracBrowser for help on using the repository browser.