source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_slicer.py @ 6d7b252b

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

improved support for py37 in sasgui

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