source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py @ 99321b2

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

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 17.8 KB
Line 
1import math
2
3import wx
4import numpy as np
5
6from sas.sasgui.guiframe.events import NewPlotEvent
7from sas.sasgui.guiframe.events import StatusEvent
8from sas.sasgui.guiframe.events import SlicerParameterEvent
9from sas.sasgui.guiframe.events import EVT_SLICER_PARS
10from sas.sasgui.guiframe.dataFitting import Data1D
11
12from .BaseInteractor import _BaseInteractor
13
14
15class BoxInteractor(_BaseInteractor):
16    """
17    BoxInteractor define a rectangle that return data1D average of Data2D
18    in a rectangle area defined by -x, x ,y, -y
19    """
20    def __init__(self, base, axes, color='black', zorder=3):
21        _BaseInteractor.__init__(self, base, axes, color=color)
22        # # Class initialization
23        self.markers = []
24        self.axes = axes
25        # #connecting artist
26        self.connect = self.base.connect
27        # # which direction is the preferred interaction direction
28        self.direction = None
29        # # determine x y  values
30        self.x = 0.5 * min(math.fabs(self.base.data2D.xmax),
31                           math.fabs(self.base.data2D.xmin))
32        self.y = 0.5 * min(math.fabs(self.base.data2D.xmax),
33                           math.fabs(self.base.data2D.xmin))
34        # # when reach qmax reset the graph
35        self.qmax = max(self.base.data2D.xmax, self.base.data2D.xmin,
36                        self.base.data2D.ymax, self.base.data2D.ymin)
37        # # Number of points on the plot
38        self.nbins = 30
39        # # If True, I(|Q|) will be return, otherwise,
40        # negative q-values are allowed
41        self.fold = True
42        # # reference of the current  Slab averaging
43        self.averager = None
44        # # Create vertical and horizaontal lines for the rectangle
45        self.vertical_lines = VerticalLines(self,
46                                            self.base.subplot,
47                                            color='blue',
48                                            zorder=zorder,
49                                            y=self.y,
50                                            x=self.x)
51        self.vertical_lines.qmax = self.qmax
52
53        self.horizontal_lines = HorizontalLines(self,
54                                                self.base.subplot,
55                                                color='green',
56                                                zorder=zorder,
57                                                x=self.x,
58                                                y=self.y)
59        self.horizontal_lines.qmax = self.qmax
60        # # draw the rectangle and plost the data 1D resulting
61        # # of averaging data2D
62        self.update()
63        self._post_data()
64        # # Bind to slice parameter events
65        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
66
67    def _onEVT_SLICER_PARS(self, event):
68        """
69        receive an event containing parameters values to reset the slicer
70
71        :param event: event of type SlicerParameterEvent with params as
72            attribute
73        """
74        wx.PostEvent(self.base.parent,
75                     StatusEvent(status="BoxSlicer._onEVT_SLICER_PARS"))
76        event.Skip()
77        if event.type == self.__class__.__name__:
78            self.set_params(event.params)
79            self.base.update()
80
81    def update_and_post(self):
82        """
83        Update the slicer and plot the resulting data
84        """
85        self.update()
86        self._post_data()
87
88    def set_layer(self, n):
89        """
90        Allow adding plot to the same panel
91
92        :param n: the number of layer
93
94        """
95        self.layernum = n
96        self.update()
97
98    def clear(self):
99        """
100        Clear the slicer and all connected events related to this slicer
101        """
102        self.averager = None
103        self.clear_markers()
104        self.horizontal_lines.clear()
105        self.vertical_lines.clear()
106        self.base.connect.clearall()
107        self.base.Unbind(EVT_SLICER_PARS)
108
109    def update(self):
110        """
111        Respond to changes in the model by recalculating the profiles and
112        resetting the widgets.
113        """
114        # #Update the slicer if an horizontal line is dragged
115        if self.horizontal_lines.has_move:
116            self.horizontal_lines.update()
117            self.vertical_lines.update(y=self.horizontal_lines.y)
118        # #Update the slicer if a vertical line is dragged
119        if self.vertical_lines.has_move:
120            self.vertical_lines.update()
121            self.horizontal_lines.update(x=self.vertical_lines.x)
122
123    def save(self, ev):
124        """
125        Remember the roughness for this layer and the next so that we
126        can restore on Esc.
127        """
128        self.base.freeze_axes()
129        self.vertical_lines.save(ev)
130        self.horizontal_lines.save(ev)
131
132    def _post_data(self):
133        pass
134
135    def post_data(self, new_slab=None, nbins=None, direction=None):
136        """
137        post data averaging in Qx or Qy given new_slab type
138
139        :param new_slab: slicer that determine with direction to average
140        :param nbins: the number of points plotted when averaging
141        :param direction: the direction of averaging
142
143        """
144        if self.direction is None:
145            self.direction = direction
146
147        x_min = -1 * math.fabs(self.vertical_lines.x)
148        x_max = math.fabs(self.vertical_lines.x)
149        y_min = -1 * math.fabs(self.horizontal_lines.y)
150        y_max = math.fabs(self.horizontal_lines.y)
151
152        if nbins is not None:
153            self.nbins = nbins
154        if self.averager is None:
155            if new_slab is None:
156                msg = "post data:cannot average , averager is empty"
157                raise ValueError(msg)
158            self.averager = new_slab
159        if self.direction == "X":
160            if self.fold:
161                x_low = 0
162            else:
163                x_low = math.fabs(x_min)
164            bin_width = (x_max + x_low) / self.nbins
165        elif self.direction == "Y":
166            if self.fold:
167                y_low = 0
168            else:
169                y_low = math.fabs(y_min)
170            bin_width = (y_max + y_low) / self.nbins
171        else:
172            msg = "post data:no Box Average direction was supplied"
173            raise ValueError(msg)
174        # # Average data2D given Qx or Qy
175        box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max,
176                            bin_width=bin_width)
177        box.fold = self.fold
178        boxavg = box(self.base.data2D)
179        # 3 Create Data1D to plot
180        if hasattr(boxavg, "dxl"):
181            dxl = boxavg.dxl
182        else:
183            dxl = None
184        if hasattr(boxavg, "dxw"):
185            dxw = boxavg.dxw
186        else:
187            dxw = None
188        new_plot = Data1D(x=boxavg.x, y=boxavg.y, dy=boxavg.dy)
189        new_plot.dxl = dxl
190        new_plot.dxw = dxw
191        new_plot.name = str(self.averager.__name__) + \
192                        "(" + self.base.data2D.name + ")"
193        new_plot.source = self.base.data2D.source
194        new_plot.interactive = True
195        new_plot.detector = self.base.data2D.detector
196        # # If the data file does not tell us what the axes are, just assume...
197        new_plot.xaxis("\\rm{Q}", "A^{-1}")
198        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
199
200        data = self.base.data2D
201        if hasattr(data, "scale") and data.scale == 'linear' and \
202                self.base.data2D.name.count("Residuals") > 0:
203            new_plot.ytransform = 'y'
204            new_plot.yaxis("\\rm{Residuals} ", "/")
205
206        new_plot.group_id = "2daverage" + self.base.data2D.name
207        new_plot.id = (self.averager.__name__) + self.base.data2D.name
208        new_plot.is_data = True
209        self.base.parent.update_theory(data_id=self.base.data2D.id, \
210                                       theory=new_plot)
211        wx.PostEvent(self.base.parent,
212                     NewPlotEvent(plot=new_plot, title=str(self.averager.__name__)))
213
214    def moveend(self, ev):
215        """
216        Called after a dragging event.
217        Post the slicer new parameters and creates a new Data1D
218        corresponding to the new average
219        """
220        self.base.thaw_axes()
221        # Post parameters
222        event = SlicerParameterEvent()
223        event.type = self.__class__.__name__
224        event.params = self.get_params()
225        wx.PostEvent(self.base.parent, event)
226        # create the new data1D
227        self._post_data()
228
229    def restore(self):
230        """
231        Restore the roughness for this layer.
232        """
233        self.horizontal_lines.restore()
234        self.vertical_lines.restore()
235
236    def move(self, x, y, ev):
237        """
238        Process move to a new position, making sure that the move is allowed.
239        """
240        pass
241
242    def set_cursor(self, x, y):
243        pass
244
245    def get_params(self):
246        """
247        Store a copy of values of parameters of the slicer into a dictionary.
248
249        :return params: the dictionary created
250
251        """
252        params = {}
253        params["x_max"] = math.fabs(self.vertical_lines.x)
254        params["y_max"] = math.fabs(self.horizontal_lines.y)
255        params["nbins"] = self.nbins
256        return params
257
258    def set_params(self, params):
259        """
260        Receive a dictionary and reset the slicer with values contained
261        in the values of the dictionary.
262
263        :param params: a dictionary containing name of slicer parameters and
264            values the user assigned to the slicer.
265        """
266        self.x = float(math.fabs(params["x_max"]))
267        self.y = float(math.fabs(params["y_max"]))
268        self.nbins = params["nbins"]
269
270        self.horizontal_lines.update(x=self.x, y=self.y)
271        self.vertical_lines.update(x=self.x, y=self.y)
272        self.post_data(nbins=None)
273
274    def freeze_axes(self):
275        """
276        """
277        self.base.freeze_axes()
278
279    def thaw_axes(self):
280        """
281        """
282        self.base.thaw_axes()
283
284    def draw(self):
285        """
286        """
287        self.base.draw()
288
289
290class HorizontalLines(_BaseInteractor):
291    """
292    Draw 2 Horizontal lines centered on (0,0) that can move
293    on the x- direction and in opposite direction
294    """
295    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
296        """
297        """
298        _BaseInteractor.__init__(self, base, axes, color=color)
299        # #Class initialization
300        self.markers = []
301        self.axes = axes
302        # # Saving the end points of two lines
303        self.x = x
304        self.save_x = x
305
306        self.y = y
307        self.save_y = y
308        # # Creating a marker
309        # Inner circle marker
310        self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
311                                           marker='s', markersize=10,
312                                           color=self.color, alpha=0.6,
313                                           pickradius=5, label="pick",
314                                           zorder=zorder,
315                                           visible=True)[0]
316        # # Define 2 horizontal lines
317        self.top_line = self.axes.plot([self.x, -self.x], [self.y, self.y],
318                                       linestyle='-', marker='',
319                                       color=self.color, visible=True)[0]
320        self.bottom_line = self.axes.plot([self.x, -self.x], [-self.y, -self.y],
321                                          linestyle='-', marker='',
322                                          color=self.color, visible=True)[0]
323        # # Flag to check the motion of the lines
324        self.has_move = False
325        # # Connecting markers to mouse events and draw
326        self.connect_markers([self.top_line, self.inner_marker])
327        self.update()
328
329    def set_layer(self, n):
330        """
331        Allow adding plot to the same panel
332
333        :param n: the number of layer
334
335        """
336        self.layernum = n
337        self.update()
338
339    def clear(self):
340        """
341        Clear this slicer  and its markers
342        """
343        self.clear_markers()
344        try:
345            self.inner_marker.remove()
346            self.top_line.remove()
347            self.bottom_line.remove()
348        except:
349            # Old version of matplotlib
350            for item in range(len(self.axes.lines)):
351                del self.axes.lines[0]
352
353    def update(self, x=None, y=None):
354        """
355        Draw the new roughness on the graph.
356
357        :param x: x-coordinates to reset current class x
358        :param y: y-coordinates to reset current class y
359
360        """
361        # # Reset x, y- coordinates if send as parameters
362        if x is not None:
363            self.x = np.sign(self.x) * math.fabs(x)
364        if y is not None:
365            self.y = np.sign(self.y) * math.fabs(y)
366        # # Draw lines and markers
367        self.inner_marker.set(xdata=[0], ydata=[self.y])
368        self.top_line.set(xdata=[self.x, -self.x], ydata=[self.y, self.y])
369        self.bottom_line.set(xdata=[self.x, -self.x], ydata=[-self.y, -self.y])
370
371    def save(self, ev):
372        """
373        Remember the roughness for this layer and the next so that we
374        can restore on Esc.
375        """
376        self.save_x = self.x
377        self.save_y = self.y
378        self.base.freeze_axes()
379
380    def moveend(self, ev):
381        """
382        Called after a dragging this edge and set self.has_move to False
383        to specify the end of dragging motion
384        """
385        self.has_move = False
386        self.base.moveend(ev)
387
388    def restore(self):
389        """
390        Restore the roughness for this layer.
391        """
392        self.x = self.save_x
393        self.y = self.save_y
394
395    def move(self, x, y, ev):
396        """
397        Process move to a new position, making sure that the move is allowed.
398        """
399        self.y = y
400        self.has_move = True
401        self.base.base.update()
402
403
404class VerticalLines(_BaseInteractor):
405    """
406    Select an annulus through a 2D plot
407    """
408    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
409        """
410        """
411        _BaseInteractor.__init__(self, base, axes, color=color)
412        self.markers = []
413        self.axes = axes
414        self.x = math.fabs(x)
415        self.save_x = self.x
416        self.y = math.fabs(y)
417        self.save_y = y
418        # Inner circle marker
419        self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
420                                           marker='s', markersize=10,
421                                           color=self.color, alpha=0.6,
422                                           pickradius=5, label="pick",
423                                           zorder=zorder, visible=True)[0]
424        self.right_line = self.axes.plot([self.x, self.x],
425                                         [self.y, -self.y],
426                                         linestyle='-', marker='',
427                                         color=self.color, visible=True)[0]
428        self.left_line = self.axes.plot([-self.x, -self.x],
429                                        [self.y, -self.y],
430                                        linestyle='-', marker='',
431                                        color=self.color, visible=True)[0]
432        self.has_move = False
433        self.connect_markers([self.right_line, self.inner_marker])
434        self.update()
435
436    def set_layer(self, n):
437        """
438        Allow adding plot to the same panel
439
440        :param n: the number of layer
441
442        """
443        self.layernum = n
444        self.update()
445
446    def clear(self):
447        """
448        Clear this slicer  and its markers
449        """
450        self.clear_markers()
451        try:
452            self.inner_marker.remove()
453            self.left_line.remove()
454            self.right_line.remove()
455        except:
456            # Old version of matplotlib
457            for item in range(len(self.axes.lines)):
458                del self.axes.lines[0]
459
460    def update(self, x=None, y=None):
461        """
462        Draw the new roughness on the graph.
463
464        :param x: x-coordinates to reset current class x
465        :param y: y-coordinates to reset current class y
466
467        """
468        # # reset x, y -coordinates if given as parameters
469        if x is not None:
470            self.x = np.sign(self.x) * math.fabs(x)
471        if y is not None:
472            self.y = np.sign(self.y) * math.fabs(y)
473        # # draw lines and markers
474        self.inner_marker.set(xdata=[self.x], ydata=[0])
475        self.left_line.set(xdata=[-self.x, -self.x], ydata=[self.y, -self.y])
476        self.right_line.set(xdata=[self.x, self.x], ydata=[self.y, -self.y])
477
478    def save(self, ev):
479        """
480        Remember the roughness for this layer and the next so that we
481        can restore on Esc.
482        """
483        self.save_x = self.x
484        self.save_y = self.y
485        self.base.freeze_axes()
486
487    def moveend(self, ev):
488        """
489        Called after a dragging this edge and set self.has_move to False
490        to specify the end of dragging motion
491        """
492        self.has_move = False
493        self.base.moveend(ev)
494
495    def restore(self):
496        """
497        Restore the roughness for this layer.
498        """
499        self.x = self.save_x
500        self.y = self.save_y
501
502    def move(self, x, y, ev):
503        """
504        Process move to a new position, making sure that the move is allowed.
505        """
506        self.has_move = True
507        self.x = x
508        self.base.base.update()
509
510
511class BoxInteractorX(BoxInteractor):
512    """
513    Average in Qx direction
514    """
515    def __init__(self, base, axes, color='black', zorder=3):
516        BoxInteractor.__init__(self, base, axes, color=color)
517        self.base = base
518        self._post_data()
519
520    def _post_data(self):
521        """
522        Post data creating by averaging in Qx direction
523        """
524        from sas.sascalc.dataloader.manipulations import SlabX
525        self.post_data(SlabX, direction="X")
526
527
528class BoxInteractorY(BoxInteractor):
529    """
530    Average in Qy direction
531    """
532    def __init__(self, base, axes, color='black', zorder=3):
533        BoxInteractor.__init__(self, base, axes, color=color)
534        self.base = base
535        self._post_data()
536
537    def _post_data(self):
538        """
539        Post data creating by averaging in Qy direction
540        """
541        from sas.sascalc.dataloader.manipulations import SlabY
542        self.post_data(SlabY, direction="Y")
543
Note: See TracBrowser for help on using the repository browser.