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

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 edc7852 was edc7852, checked in by krzywon, 7 years ago

Incorporate log binning into sector parameter window.

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