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

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

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 13.4 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        else:
147            data_list = list(data_list)  # force iterator to list
148        if len(data_list) >= 1:
149            if len(data_list) == 1:
150                data = data_list[0]
151            else:
152                data_1d_list = []
153                data_2d_list = []
154                error_msg = ""
155                # separate data into data1d and data2d list
156                for data in data_list:
157                    if data is not None:
158                        if issubclass(data.__class__, Data1D):
159                            data_1d_list.append(data)
160                        else:
161                            error_msg += " %s  type %s \n" % (str(data.name),
162                                                              str(data.__class__.__name__))
163                            data_2d_list.append(data)
164                if len(data_2d_list) > 0:
165                    msg = "Invariant does not support the following data types:\n"
166                    msg += error_msg
167                if len(data_1d_list) == 0:
168                    wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
169                    return
170                msg += "Invariant panel does not allow multiple data!\n"
171                msg += "Please select one.\n"
172                if len(data_list) > 1:
173                    from invariant_widgets import DataDialog
174                    dlg = DataDialog(data_list=data_1d_list, text=msg)
175                    if dlg.ShowModal() == wx.ID_OK:
176                        data = dlg.get_data()
177                    else:
178                        data = None
179                    dlg.Destroy()
180
181            if data is None:
182                msg += "invariant receives no data. \n"
183                wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
184                return
185            if not issubclass(data.__class__, Data1D):
186                msg += "invariant cannot be computed for data of "
187                msg += "type %s\n" % (data.__class__.__name__)
188                wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
189                return
190            else:
191                wx.PostEvent(self.parent, NewPlotEvent(plot=data, title=data.title))
192                try:
193                    self.compute_helper(data)
194                except Exception as exc:
195                    msg = "Invariant Set_data: " + str(exc)
196                    wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
197        else:
198            msg = "invariant cannot be computed for data of "
199            msg += "type %s" % (data.__class__.__name__)
200            wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
201
202    def delete_data(self, data_id):
203        """
204        """
205        if self.__data is None:
206            return
207        for id in data_id:
208            if id == self.__data.id:
209                self.clear_panel()
210
211    def clear_panel(self):
212        """
213        """
214        self.invariant_panel.clear_panel()
215
216    def compute_helper(self, data):
217        """
218        """
219        if data is None:
220            return
221        # set current data if not it's a state data
222        if not self.invariant_panel.is_state_data:
223            # Store reference to data
224            self.__data = data
225            # Set the data set to be user for invariant calculation
226            self.invariant_panel.set_data(data=data)
227
228    def save_file(self, filepath, state=None):
229        """
230        Save data in provided state object.
231
232        :param filepath: path of file to write to
233        :param state: invariant state
234        """
235        # Write the state to file
236        # First, check that the data is of the right type
237        current_plottable = self.__data
238
239        if issubclass(current_plottable.__class__, Data1D):
240            self.state_reader.write(filepath, current_plottable, state)
241        else:
242            msg = "invariant.save_file: the data being saved is"
243            msg += " not a sas.sascalc.dataloader.data_info.Data1D object"
244            raise RuntimeError(msg)
245
246    def set_state(self, state=None, datainfo=None):
247        """
248        Call-back method for the state reader.
249        This method is called when a .inv/.svs file is loaded.
250
251        :param state: State object
252        """
253        self.temp_state = None
254        try:
255            if datainfo.__class__.__name__ == 'list':
256                data = datainfo[0]
257            else:
258                data = datainfo
259            if data is None:
260                msg = "invariant.set_state: datainfo parameter cannot"
261                msg += " be None in standalone mode"
262                raise RuntimeError(msg)
263            # Make sure the user sees the invariant panel after loading
264            # self.parent.set_perspective(self.perspective)
265            self.on_perspective(event=None)
266            name = data.meta_data['invstate'].file
267            data.meta_data['invstate'].file = name
268            data.name = name
269            data.filename = name
270
271            data = self.parent.create_gui_data(data, None)
272            self.__data = data
273            wx.PostEvent(self.parent, NewPlotEvent(plot=self.__data,
274                                                   reset=True, title=self.__data.title))
275            data_dict = {self.__data.id:self.__data}
276            self.parent.add_data(data_list=data_dict)
277            # set state
278            self.invariant_panel.is_state_data = True
279
280            # Load the invariant states
281            self.temp_state = state
282            # Requires to have self.__data and self.temp_state  first.
283            self.on_set_state_helper(None)
284
285        except Exception as exc:
286            logger.error("invariant.set_state: %s" % exc)
287
288    def on_set_state_helper(self, event=None):
289        """
290        Set the state when called by EVT_STATE_UPDATE event from guiframe
291        after a .inv/.svs file is loaded
292        """
293        self.invariant_panel.set_state(state=self.temp_state,
294                                       data=self.__data)
295        self.temp_state = None
296
297
298    def plot_theory(self, data=None, name=None):
299        """
300        Receive a data set and post a NewPlotEvent to parent.
301
302        :param data: extrapolated data to be plotted
303        :param name: Data's name to use for the legend
304        """
305        # import copy
306        if data is None:
307            id = str(self.__data.id) + name
308            group_id = self.__data.group_id
309            wx.PostEvent(self.parent, NewPlotEvent(id=id, group_id=group_id, action='Remove'))
310            return
311
312        new_plot = Data1D(x=[], y=[], dy=None)
313        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
314        scale = self.invariant_panel.get_scale()
315        background = self.invariant_panel.get_background()
316
317        if scale != 0:
318            # Put back the sacle and bkg for plotting
319            data.y = (data.y + background) / scale
320            new_plot = Data1D(x=data.x, y=data.y, dy=None)
321            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
322        else:
323            msg = "Scale can not be zero."
324            raise ValueError(msg)
325        if len(new_plot.x) == 0:
326            return
327
328        new_plot.name = name
329        new_plot.xaxis(self.__data._xaxis, self.__data._xunit)
330        new_plot.yaxis(self.__data._yaxis, self.__data._yunit)
331        new_plot.group_id = self.__data.group_id
332        new_plot.id = str(self.__data.id) + name
333        new_plot.title = self.__data.title
334        # Save theory_data in a state
335        if data is not None:
336            name_head = name.split('-')
337            if name_head[0] == 'Low':
338                self.invariant_panel.state.theory_lowQ = copy.deepcopy(new_plot)
339            elif name_head[0] == 'High':
340                self.invariant_panel.state.theory_highQ = copy.deepcopy(new_plot)
341
342        self.parent.update_theory(data_id=self.__data.id, theory=new_plot)
343        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
344                                               title=self.__data.title))
345
346    def plot_data(self, scale, background):
347        """
348        replot the current data if the user enters a new scale or background
349        """
350        new_plot = scale * self.__data - background
351        new_plot.name = self.__data.name
352        new_plot.group_id = self.__data.group_id
353        new_plot.id = self.__data.id
354        new_plot.title = self.__data.title
355
356        # Save data in a state: but seems to never happen
357        if new_plot is not None:
358            self.invariant_panel.state.data = copy.deepcopy(new_plot)
359        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
360                                               title=new_plot.title))
361
Note: See TracBrowser for help on using the repository browser.