source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_slicer.py @ 8e15dce

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 8e15dce was 8e15dce, checked in by krzywon, 7 years ago

Modified manipulations to clarify what is needed. Save sliced data as txt by default, instead of xml, to speed up data process. numpy → np.

  • Property mode set to 100644
File size: 20.7 KB
Line 
1
2
3import os
4import wx
5import wx.lib.newevent
6from sas.sascalc.dataloader.readers.cansas_reader import Reader
7from sas.sasgui.guiframe.events import EVT_SLICER_PARS
8from sas.sasgui.guiframe.utils import format_number
9from sas.sasgui.guiframe.events import EVT_SLICER
10from sas.sasgui.guiframe.events import SlicerParameterEvent
11from Plotter2D import ModelPanel2D
12apply_params, EVT_APPLY_PARAMS = wx.lib.newevent.NewEvent()
13save_files, EVT_AUTO_SAVE = wx.lib.newevent.NewEvent()
14
15FIT_OPTIONS = ["No fitting", "Fitting", "Batch Fitting"]
16CONVERT_KEYS = ["SectorInteractor", "AnnulusInteractor", "BoxInteractorX",
17                "BoxInteractorY"]
18CONVERT_DICT = {"SectorInteractor": "SectorQ",
19                "AnnulusInteractor": "AnnulusPhi",
20                "BoxInteractorX": "SlabX",
21                "BoxInteractorY": "SlabY"}
22
23
24class SlicerParameterPanel(wx.Dialog):
25    """
26    Panel for dynamically changing slicer parameters and apply the same slicer
27    to multiple 2D plot panels
28    """
29
30    def __init__(self, parent, *args, **kwargs):
31        """
32        Dialog window that allow to edit parameters slicer
33        by entering new values
34        """
35        wx.Dialog.__init__(self, parent, *args, **kwargs)
36        self.params = {}
37        self.iter = 0
38        self.parent = parent
39        self.main_window = parent.parent
40        self.data_panel = self.main_window._data_panel
41        self.type = None
42        self.listeners = []
43        self.parameters = []
44        self.bck = wx.GridBagSizer(5, 5)
45        self.SetSizer(self.bck)
46        self.auto_save = None
47        self.path = None
48        self.fitting_options = None
49        self.type_list = []
50        self.loaded_data = []
51        self.always_on = None
52        self.type_select = None
53        self.append_name = None
54        self.data_list = None
55        self.default_value = ""
56        self.batch_slicer_button = None
57        label = "Right-click on 2D plot for slicer options"
58        title = wx.StaticText(self, -1, label, style=wx.ALIGN_LEFT)
59        self.bck.Add(title, (0, 0), (1, 2),
60                     flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border=15)
61        # Bindings
62        self.parent.Bind(EVT_SLICER, self.on_evt_slicer)
63        self.Bind(EVT_SLICER_PARS, self.on_param_change)
64        self.Bind(EVT_APPLY_PARAMS, self.apply_params_list_and_process)
65        self.Bind(EVT_AUTO_SAVE, self.save_files)
66
67    def on_evt_slicer(self, event):
68        """
69        Process EVT_SLICER events
70        When the slicer changes, update the panel
71
72        :param event: EVT_SLICER event
73        """
74        event.Skip()
75        if event.obj_class is None:
76            self.set_slicer(None, None)
77        else:
78            self.set_slicer(event.type, event.params)
79
80    def set_slicer(self, type, params):
81        """
82        Rebuild the panel
83        """
84        self.bck.Clear(True)
85        self.bck.Add((5, 5), (0, 0), (1, 1),
86                     wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
87        self.type = type
88        if type is None:
89            label = "Right-click on 2D plot for slicer options"
90            title = wx.StaticText(self, -1, label, style=wx.ALIGN_LEFT)
91            self.bck.Add(title, (1, 0), (1, 2),
92                         flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border=15)
93        else:
94            title = wx.StaticText(self, -1,
95                                  "Slicer Parameters:", style=wx.ALIGN_LEFT)
96            self.bck.Add(title, (1, 0), (1, 2),
97                         flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border=15)
98            iy = 1
99            self.parameters = []
100            keys = params.keys()
101            keys.sort()
102            for item in keys:
103                iy += 1
104                ix = 0
105                if item not in ["count", "errors"]:
106                    text = wx.StaticText(self, -1, item, style=wx.ALIGN_LEFT)
107                    self.bck.Add(text, (iy, ix), (1, 1),
108                                 wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
109                    ctl = wx.TextCtrl(self, -1, size=(80, 20),
110                                      style=wx.TE_PROCESS_ENTER)
111                    hint_msg = "Modify the value of %s to change" % item
112                    hint_msg += " the 2D slicer"
113                    ctl.SetToolTipString(hint_msg)
114                    ix = 1
115                    ctl.SetValue(format_number(str(params[item])))
116                    self.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter)
117                    self.parameters.append([item, ctl])
118                    self.bck.Add(ctl, (iy, ix), (1, 1),
119                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
120                    ix = 3
121                    self.bck.Add((20, 20), (iy, ix), (1, 1),
122                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
123                else:
124                    text = wx.StaticText(self, -1, item + " : ",
125                                         style=wx.ALIGN_LEFT)
126                    self.bck.Add(text, (iy, ix), (1, 1),
127                                 wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
128                    ctl = wx.StaticText(self, -1,
129                                        format_number(str(params[item])),
130                                        style=wx.ALIGN_LEFT)
131                    ix = 1
132                    self.bck.Add(ctl, (iy, ix), (1, 1),
133                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
134
135            # Change slicer within the window
136            ix = 0
137            iy += 1
138            txt = "Slicer type:"
139            text = wx.StaticText(self, -1, txt, style=wx.ALIGN_LEFT)
140            self.bck.Add(text, (iy, ix), (1, 1),
141                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
142            self.type_list = CONVERT_KEYS
143            self.type_select = wx.ComboBox(parent=self, choices=self.type_list)
144            self.type_select.Bind(wx.EVT_COMBOBOX, self.on_change_slicer)
145            index = self.type_select.FindString(type)
146            self.type_select.SetSelection(index)
147            self.bck.Add(self.type_select, (iy, 1), (1, 1),
148                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
149
150            # batch slicing parameters
151            title_text = "Batch Slicing Options:"
152            title = wx.StaticText(self, -1, title_text, style=wx.ALIGN_LEFT)
153            iy += 1
154            line = wx.StaticLine(self, -1, style=wx.LI_VERTICAL)
155            line.SetSize((60, 60))
156            self.bck.Add(line, (iy, ix), (1, 2),
157                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
158            iy += 1
159            self.bck.Add(title, (iy, ix), (1, 1),
160                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
161
162            # Create a list box with all of the 2D plots
163            iy += 1
164            self.process_list()
165            self.bck.Add(self.data_list, (iy, ix), (1, 1),
166                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
167
168            # Checkbox to enable saving and fitting options
169            iy += 1
170            self.auto_save = wx.CheckBox(parent=self, id=wx.NewId(),
171                                         label="Auto save generated 1D:")
172            self.Bind(wx.EVT_CHECKBOX, self.on_auto_save_checked)
173            self.bck.Add(self.auto_save, (iy, ix), (1, 1),
174                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
175            iy += 1
176            # File browser
177            save_to = "Save files to:"
178            save = wx.StaticText(self, -1, save_to, style=wx.ALIGN_LEFT)
179            path = os.getcwd()
180            self.path = wx.DirPickerCtrl(self, id=wx.NewId(), path=path,
181                                         message=save_to)
182            self.path.Enable(False)
183            self.bck.Add(save, (iy, ix), (1, 1),
184                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
185            self.bck.Add(self.path, (iy, 1), (1, 1),
186                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
187            # Append to file
188            iy += 1
189            self.update_file_append(params)
190            append_text = "Append to file name:"
191            append = wx.StaticText(self, -1, append_text, style=wx.ALIGN_LEFT)
192            self.append_name = wx.TextCtrl(parent=self, id=wx.NewId(),
193                                           name="Append to file name:")
194            append_tool_tip = "Files will be saved as <SlicerType><FileName>"
195            append_tool_tip += "<AppendToText>.txt"
196            self.append_name.SetToolTipString(append_tool_tip)
197            self.append_name.SetValue(self.default_value)
198            self.append_name.Enable(False)
199            self.bck.Add(append, (iy, ix), (1, 1),
200                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
201            self.bck.Add(self.append_name, (iy, 1), (1, 1),
202                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
203
204            # Combobox for selecting fitting options
205            iy += 1
206            fit_text = "Fitting Options:"
207            fit_text_item = wx.StaticText(self, -1, fit_text,
208                                          style=wx.ALIGN_LEFT)
209            self.bck.Add(fit_text_item, (iy, ix), (1, 1),
210                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
211            self.fitting_options = wx.ComboBox(parent=self, choices=FIT_OPTIONS)
212            self.fitting_options.SetSelection(0)
213            self.bck.Add(self.fitting_options, (iy, 1), (1, 1),
214                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
215            self.fitting_options.Enable(False)
216            self.fitting_options.Bind(wx.EVT_COMBOBOX, None)
217
218            # Button to start batch slicing
219            iy += 1
220            button_label = "Apply Slicer to Selected Plots"
221            self.batch_slicer_button = wx.Button(parent=self,
222                                                 label=button_label)
223            self.Bind(wx.EVT_BUTTON, self.on_batch_slicer)
224            self.bck.Add(self.batch_slicer_button, (iy, ix), (1, 1),
225                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
226            iy += 1
227            self.bck.Add((5, 5), (iy, ix), (1, 1),
228                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
229        self.bck.Layout()
230        self.bck.Fit(self)
231        self.parent.GetSizer().Layout()
232
233    def on_param_change(self, evt):
234        """
235        receive an event end reset value text fields
236        inside self.parameters
237        """
238        evt.Skip()
239        if evt.type == "UPDATE":
240            for item in self.parameters:
241                if item[0] in evt.params:
242                    item[1].SetValue("%-5.3g" % evt.params[item[0]])
243                    item[1].Refresh()
244
245    def on_text_enter(self, evt):
246        """
247        Parameters have changed
248        """
249        params = {}
250        has_error = False
251        for item in self.parameters:
252            try:
253                params[item[0]] = float(item[1].GetValue())
254                item[1].SetBackgroundColour(
255                    wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
256                item[1].Refresh()
257            except:
258                has_error = True
259                item[1].SetBackgroundColour("pink")
260                item[1].Refresh()
261
262        if not has_error:
263            # Post parameter event
264            # parent here is plotter2D
265            self.update_file_append(params)
266            self.append_name.SetValue(self.default_value)
267            self.append_name.Refresh()
268            event = SlicerParameterEvent(type=self.type, params=params)
269            wx.PostEvent(self.parent, event)
270
271    def on_batch_slicer(self, evt=None):
272        """
273        Event triggered when batch slicing button is pressed
274        :param evt: Event triggering the batch slicing
275        """
276        apply_to_list = []
277        spp = self.parent.parent
278        params = self.parent.slicer.get_params()
279        slicer_type = self.type_select.GetStringSelection()
280        save = self.auto_save.IsChecked()
281        append = self.append_name.GetValue()
282        path = self.path.GetPath()
283        fit = self.fitting_options.GetValue()
284
285        # Find desired 2D data panels
286        for key, mgr in spp.plot_panels.iteritems():
287            if mgr.graph.prop['title'] in self.data_list.CheckedStrings:
288                apply_to_list.append(mgr)
289
290        # Apply slicer type to selected panels
291        for item in apply_to_list:
292            self._apply_slicer_to_plot(item, slicer_type)
293
294        # Post an event to apply appropriate slicer params to each slicer
295        # Pass all variables, including class variables
296        event_params = apply_params(params=params, apply_to_list=apply_to_list,
297                                    auto_save=save, append=append, fit=fit,
298                                    path=path, type=slicer_type)
299        wx.PostEvent(self, event_params)
300
301    def on_change_slicer(self, evt):
302        """
303        Event driven slicer change when self.type_select changes
304        :param evt: Event triggering this change
305        """
306        self._apply_slicer_to_plot(self.parent)
307
308    def _apply_slicer_to_plot(self, plot, slicer_type=None):
309        """
310        Apply a slicer to *any* plot window, not just parent window
311        :param plot: 2D plot panel to apply a slicer to
312        :param slicer_type: The type of slicer to apply to the panel
313        """
314        # Skip redrawing the current plot if no change in slicer type
315        if self.parent == plot and self.type == slicer_type:
316            return
317        # Do not draw a slicer on a 1D plot
318        if not isinstance(plot, ModelPanel2D):
319            return
320        if slicer_type is None:
321            slicer_type = self.type_select.GetStringSelection()
322        if slicer_type == self.type_list[0]:
323            plot.onSectorQ(None)
324        elif slicer_type == self.type_list[1]:
325            plot.onSectorPhi(None)
326        elif slicer_type == self.type_list[2]:
327            plot.onBoxavgX(None)
328        elif slicer_type == self.type_list[3]:
329            plot.onBoxavgY(None)
330
331    def process_list(self):
332        """
333        Populate the check list from the currently plotted 2D data
334        """
335        # Reinitialize loaded data list on redraw
336        self.loaded_data = []
337        # Iterate over the loaded plots and find all 2D panels
338        for key, value in self.main_window.plot_panels.iteritems():
339            if isinstance(value, ModelPanel2D):
340                self.loaded_data.append(value.data2D.name)
341                if value.data2D.id == self.parent.data2D.id:
342                    # Set current plot panel as uncheckable
343                    self.always_on = self.loaded_data.index(value.data2D.name)
344        self.data_list = wx.CheckListBox(parent=self, id=wx.NewId(),
345                                         choices=self.loaded_data,
346                                         name="Apply Slicer to 2D Plots:")
347        # Check all items by default
348        for item in range(len(self.data_list.Items)):
349            self.data_list.Check(item)
350        self.data_list.Bind(wx.EVT_CHECKLISTBOX, self.on_check_box_list)
351
352    def on_check_box_list(self, evt=None):
353        """
354        Prevent a checkbox item from being unchecked
355        :param evt: Event triggered when a checkbox list item is checked
356        """
357        if evt is None:
358            return
359        index = evt.GetSelection()
360        if index == self.always_on:
361            self.data_list.Check(index)
362
363    def apply_params_list_and_process(self, evt=None):
364        """
365        Event based parameter setting.
366        :param evt: Event triggered to apply parameters to a list of plots
367                    evt should have attrs plot_list and params
368        """
369        if evt is None:
370            return
371        # Apply parameter list to each plot as desired
372        for item in evt.apply_to_list:
373            event = SlicerParameterEvent(type=evt.type, params=evt.params)
374            wx.PostEvent(item, event)
375        # Post an event to save each data set to file
376        if evt.auto_save:
377            event = save_files(append_to_name=evt.append, path=evt.path,
378                               type=evt.type, file_list=evt.apply_to_list,
379                               fit=evt.fit)
380            wx.PostEvent(self, event)
381
382    def save_files(self, evt=None):
383        """
384        Automatically save the sliced data to file.
385        :param evt: Event that triggered the call to the method
386        """
387
388        # Events triggered after this event pass other events to wx that are
389        # necessary before this event is called. If this is the first time
390        # reaching this event, send it to the end of the wx event queue
391        if self.iter == 0:
392            clone = evt.Clone()
393            wx.PostEvent(self, clone)
394            self.iter += 1
395            return
396        if evt is None:
397            return
398
399        # Start definitions
400        writer = Reader()
401        data_dic = {}
402        append = evt.append_to_name
403        names = []
404        f_name_list = []
405        f_path_list = []
406
407        # Get list of 2D data names for saving
408        for f_name in evt.file_list:
409            names.append(f_name.data2D.label)
410
411        # Find the correct plots to save
412        for key, plot in self.main_window.plot_panels.iteritems():
413            if not hasattr(plot, "data2D"):
414                for item in plot.plots:
415                    base = item.replace(CONVERT_DICT[evt.type], "")
416                    if base in names:
417                        data_dic[item] = plot.plots[item]
418
419        # Save files as Text
420        for item, data1d in data_dic.iteritems():
421            base = '.'.join(item.split('.')[:-1])
422            file_name = base + append + ".txt"
423            save_to = evt.path + "\\" + file_name
424            writer.write(save_to, data1d)
425            f_path_list.append(save_to)
426            f_name_list.append(file_name)
427
428        # Load files into GUI
429        for item in f_path_list:
430            self.main_window.load_data(item)
431
432        # Send to fitting
433        self.send_to_fitting(evt.fit, f_name_list)
434
435    def send_to_fitting(self, fit=FIT_OPTIONS[0], file_list=None):
436        """
437        Send a list of data to the fitting perspective
438        :param fit: fit type desired
439        :param file_list: list of loaded file names to send to fit
440        """
441        if fit in FIT_OPTIONS and fit != FIT_OPTIONS[0] and \
442                        file_list is not None:
443            # Set perspective to fitting
444            int = self.data_panel.perspective_cbox.FindString("Fitting")
445            self.data_panel.perspective_cbox.SetSelection(int)
446            self.data_panel._on_perspective_selection(None)
447            # Unselect all loaded data
448            self.data_panel.selection_cbox.SetValue('Unselect all Data')
449            self.data_panel._on_selection_type(None)
450            # Click each sliced data file
451            for f_name in file_list:
452                num = len(f_name)
453                data_list = self.data_panel.list_cb_data
454                for key in data_list:
455                    loaded_key = (key[:num]) if len(key) > num else key
456                    if loaded_key == f_name:
457                        selection = key
458                        data_ctrl = data_list[selection][0]
459                        self.check_item_and_children(data_ctrl=data_ctrl,
460                                                     check_value=True)
461            # Switch to batch mode if selected
462            if fit == FIT_OPTIONS[2]:
463                self.data_panel.rb_single_mode.SetValue(False)
464                self.data_panel.rb_batch_mode.SetValue(True)
465                self.data_panel.on_batch_mode(None)
466            else:
467                self.data_panel.rb_single_mode.SetValue(True)
468                self.data_panel.rb_batch_mode.SetValue(False)
469                self.data_panel.on_single_mode(None)
470
471            # Post button click event to send data to fitting
472            evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId,
473                                    self.data_panel.bt_import.GetId())
474            wx.PostEvent(self.data_panel, evt)
475
476    def on_auto_save_checked(self, evt=None):
477        """
478        Enable/Disable auto append when checkbox is checked
479        :param evt: Event
480        """
481        self.append_name.Enable(self.auto_save.IsChecked())
482        self.path.Enable(self.auto_save.IsChecked())
483        self.fitting_options.Enable(self.auto_save.IsChecked())
484
485    def check_item_and_children(self, data_ctrl, check_value=True):
486        self.data_panel.tree_ctrl.CheckItem(data_ctrl, check_value)
487        if data_ctrl.HasChildren():
488            if check_value and not data_ctrl.IsExpanded():
489                return
490            for child_ctrl in data_ctrl.GetChildren():
491                self.data_panel.CheckItem(child_ctrl, check_value)
492
493    def update_file_append(self, params=None):
494        """
495        Update default_value when any parameters are changed
496        :param params: dictionary of parameters
497        """
498        self.default_value = ""
499        if params is None:
500            params = self.params
501        for key in params:
502            self.default_value += "_{0}".format(key).split(" [")[0]
503            self.default_value += "-{:.2f}".format(params[key])
Note: See TracBrowser for help on using the repository browser.