source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py @ 20fa5fe

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 20fa5fe was 20fa5fe, checked in by Stuart Prescott <stuart@…>, 2 years ago

Fix lots more typos in comments and docs

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