source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/boxSlicer.py @ bc3632f

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.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since bc3632f was 8c347a6, checked in by Gervaise Alina <gervyh@…>, 13 years ago

moving guiframe under sansguiframe

  • Property mode set to 100644
File size: 19.7 KB
Line 
1
2
3import wx
4#import copy
5#from copy import deepcopy
6import math
7import numpy
8from sans.guiframe.events import NewPlotEvent
9from sans.guiframe.events import StatusEvent
10from sans.guiframe.events import SlicerParameterEvent
11from sans.guiframe.events import EVT_SLICER_PARS
12from BaseInteractor import _BaseInteractor
13from sans.guiframe.dataFitting import Data1D
14#import SlicerParameters
15
16
17class BoxInteractor(_BaseInteractor):
18    """
19    BoxInteractor define a rectangle that return data1D average of Data2D
20    in a rectangle area defined by -x, x ,y, -y
21    """
22    def __init__(self, base, axes, color='black', zorder=3):
23        _BaseInteractor.__init__(self, base, axes, color=color)
24        ## Class initialization
25        self.markers = []
26        self.axes = axes
27        ##connecting artist
28        self.connect = self.base.connect
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        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 != None:
150            self.nbins = nbins
151        if self.averager == None:
152            if new_slab == None:
153                msg = "post data:cannot average , averager is empty"
154                raise ValueError, msg
155            self.averager = new_slab
156        if 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        else:
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        ## Average data2D given Qx or Qy
169        box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max,
170                         bin_width=bin_width)
171        box.fold = self.fold
172        boxavg = box(self.base.data2D)
173        #3 Create Data1D to plot
174        if hasattr(boxavg, "dxl"):
175            dxl = boxavg.dxl
176        else:
177            dxl = None
178        if hasattr(boxavg, "dxw"):
179            dxw = boxavg.dxw
180        else:
181            dxw = None
182        new_plot = Data1D(x=boxavg.x, y=boxavg.y, dy=boxavg.dy)
183        new_plot.dxl  = dxl
184        new_plot.dxw  = dxw
185        new_plot.name = str(self.averager.__name__) + \
186                        "(" + self.base.data2D.name + ")"
187        new_plot.source = self.base.data2D.source
188        new_plot.interactive = True
189        new_plot.detector = self.base.data2D.detector
190        ## If the data file does not tell us what the axes are, just assume...
191        new_plot.xaxis("\\rm{Q}", "A^{-1}")
192        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
193
194        data = self.base.data2D
195        if hasattr(data, "scale") and data.scale == 'linear' and \
196                self.base.data2D.name.count("Residuals") > 0:
197            new_plot.ytransform = 'y'
198            new_plot.yaxis("\\rm{Residuals} ", "/")
199           
200        new_plot.group_id = str(self.averager.__name__) + self.base.data2D.name
201        #new_plot.id = str(self.averager.__name__)
202        #new_plot.is_data= True
203        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
204                                title=str(self.averager.__name__)))
205         
206    def moveend(self, ev):
207        """
208        Called after a dragging event.
209        Post the slicer new parameters and creates a new Data1D
210        corresponding to the new average
211        """
212        self.base.thaw_axes()
213        # Post paramters
214        event = SlicerParameterEvent()
215        event.type = self.__class__.__name__
216        event.params = self.get_params()
217        wx.PostEvent(self.base.parent, event)
218        # create the new data1D
219        self._post_data()
220           
221    def restore(self):
222        """
223        Restore the roughness for this layer.
224        """
225        self.horizontal_lines.restore()
226        self.vertical_lines.restore()
227       
228    def move(self, x, y, ev):
229        """
230        Process move to a new position, making sure that the move is allowed.
231        """
232        pass
233       
234    def set_cursor(self, x, y):
235        pass
236       
237    def get_params(self):
238        """
239        Store a copy of values of parameters of the slicer into a dictionary.
240       
241        :return params: the dictionary created
242       
243        """
244        params = {}
245        params["x_max"] = math.fabs(self.vertical_lines.x)
246        params["y_max"] = math.fabs(self.horizontal_lines.y)
247        params["nbins"] = self.nbins
248        return params
249   
250    def set_params(self, params):
251        """
252        Receive a dictionary and reset the slicer with values contained
253        in the values of the dictionary.
254       
255        :param params: a dictionary containing name of slicer parameters and
256            values the user assigned to the slicer.
257        """
258        self.x = float(math.fabs(params["x_max"]))
259        self.y = float(math.fabs(params["y_max"] ))
260        self.nbins = params["nbins"]
261       
262        self.horizontal_lines.update(x=self.x, y=self.y)
263        self.vertical_lines.update(x=self.x, y=self.y)
264        self.post_data(nbins=None)
265       
266    def freeze_axes(self):
267        """
268        """
269        self.base.freeze_axes()
270       
271    def thaw_axes(self):
272        """
273        """
274        self.base.thaw_axes()
275
276    def draw(self):
277        """
278        """
279        self.base.draw()
280
281
282class HorizontalLines(_BaseInteractor):
283    """
284    Draw 2 Horizontal lines centered on (0,0) that can move
285    on the x- direction and in opposite direction
286    """
287    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
288        """
289        """
290        _BaseInteractor.__init__(self, base, axes, color=color)
291        ##Class initialization
292        self.markers = []
293        self.axes = axes
294        ## Saving the end points of two lines
295        self.x = x
296        self.save_x = x
297       
298        self.y = y
299        self.save_y = y
300        ## Creating a marker
301        try:
302            # Inner circle marker
303            self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
304                                          marker='s', markersize=10,
305                                          color=self.color, alpha=0.6,
306                                          pickradius=5, label="pick", 
307                                          # Prefer this to other lines
308                                          zorder=zorder, 
309                                          visible=True)[0]
310        except:
311            self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
312                                          marker='s', markersize=10,
313                                          color=self.color, alpha=0.6,
314                                          label="pick", visible=True)[0]
315            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION "
316            message += "OF MATPLOTLIB\n Get the SVN version that"
317            message += " is at least as recent as June 1, 2007"
318            owner = self.base.base.parent
319            wx.PostEvent(owner,
320                         StatusEvent(status="AnnulusSlicer: %s" % message))
321        ## Define 2 horizontal lines
322        self.top_line = self.axes.plot([self.x, -self.x], [self.y, self.y],
323                                      linestyle='-', marker='',
324                                      color=self.color, visible=True)[0]
325        self.bottom_line = self.axes.plot([self.x, -self.x], [-self.y, -self.y],
326                                      linestyle='-', marker='',
327                                      color=self.color, visible=True)[0]
328        ## Flag to check the motion of the lines
329        self.has_move = False
330        ## Connecting markers to mouse events and draw
331        self.connect_markers([self.top_line, self.inner_marker])
332        self.update()
333
334    def set_layer(self, n):
335        """
336        Allow adding plot to the same panel
337       
338        :param n: the number of layer
339       
340        """
341        self.layernum = n
342        self.update()
343       
344    def clear(self):
345        """
346        Clear this slicer  and its markers
347        """
348        self.clear_markers()
349        try:
350            self.inner_marker.remove()
351            self.top_line.remove() 
352            self.bottom_line.remove()
353        except:
354            # Old version of matplotlib
355            for item in range(len(self.axes.lines)):
356                del self.axes.lines[0]
357   
358    def update(self, x=None, y=None):
359        """
360        Draw the new roughness on the graph.
361       
362        :param x: x-coordinates to reset current class x
363        :param y: y-coordinates to reset current class y
364       
365        """
366        ## Reset x, y- coordinates if send as parameters
367        if x != None:
368            self.x = numpy.sign(self.x) * math.fabs(x)
369        if y != None:
370            self.y = numpy.sign(self.y) * math.fabs(y)
371        ## Draw lines and markers
372        self.inner_marker.set(xdata=[0], ydata=[self.y])
373        self.top_line.set(xdata=[self.x, -self.x], ydata=[self.y, self.y])
374        self.bottom_line.set(xdata=[self.x,-self.x], ydata=[-self.y, -self.y])
375       
376    def save(self, ev):
377        """
378        Remember the roughness for this layer and the next so that we
379        can restore on Esc.
380        """
381        self.save_x = self.x
382        self.save_y = self.y
383        self.base.freeze_axes()
384
385    def moveend(self, ev):
386        """
387        Called after a dragging this edge and set self.has_move to False
388        to specify the end of dragging motion
389        """
390        self.has_move = False
391        self.base.moveend(ev)
392             
393    def restore(self):
394        """
395        Restore the roughness for this layer.
396        """
397        self.x = self.save_x
398        self.y = self.save_y
399       
400    def move(self, x, y, ev):
401        """
402        Process move to a new position, making sure that the move is allowed.
403        """
404        self.y = y
405        self.has_move = True
406        self.base.base.update()
407       
408 
409class VerticalLines(_BaseInteractor):
410    """
411    Select an annulus through a 2D plot
412    """
413    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
414        """
415        """
416        _BaseInteractor.__init__(self, base, axes, color=color)
417        self.markers = []
418        self.axes = axes
419        self.x = math.fabs(x)
420        self.save_x = self.x
421        self.y = math.fabs(y)
422        self.save_y = y
423        try:
424            # Inner circle marker
425            self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
426                                          marker='s', markersize=10,
427                                          color=self.color, alpha=0.6,
428                                          pickradius=5, label="pick", 
429                                          # Prefer this to other lines
430                                          zorder=zorder, visible=True)[0]
431        except:
432            self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
433                                          marker='s', markersize=10,
434                                          color=self.color, alpha=0.6,
435                                          label="pick", visible=True)[0]
436            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION"
437            message += " OF MATPLOTLIB\n Get the SVN version that is"
438            message += " at least as recent as June 1, 2007"
439           
440        self.right_line = self.axes.plot([self.x, self.x],
441                                         [self.y , -self.y],
442                                      linestyle='-', marker='',
443                                      color=self.color, visible=True)[0]
444        self.left_line = self.axes.plot([-self.x, -self.x],
445                                        [self.y, -self.y],
446                                      linestyle='-', marker='',
447                                      color=self.color, visible=True)[0]
448        self.has_move = False
449        self.connect_markers([self.right_line, self.inner_marker])
450        self.update()
451
452    def set_layer(self, n):
453        """
454        Allow adding plot to the same panel
455       
456        :param n: the number of layer
457       
458        """
459        self.layernum = n
460        self.update()
461       
462    def clear(self):
463        """
464        Clear this slicer  and its markers
465        """
466        self.clear_markers()
467        try:
468            self.inner_marker.remove()
469            self.left_line.remove()
470            self.right_line.remove()
471        except:
472            # Old version of matplotlib
473            for item in range(len(self.axes.lines)):
474                del self.axes.lines[0]
475
476    def update(self, x=None, y=None):
477        """
478        Draw the new roughness on the graph.
479       
480        :param x: x-coordinates to reset current class x
481        :param y: y-coordinates to reset current class y
482       
483        """
484        ## reset x, y -coordinates if given as parameters
485        if x != None:
486            self.x = numpy.sign(self.x) * math.fabs(x)
487        if y != None:
488            self.y = numpy.sign(self.y) * math.fabs(y)
489        ## draw lines and markers 
490        self.inner_marker.set(xdata=[self.x], ydata=[0]) 
491        self.left_line.set(xdata=[-self.x, -self.x], ydata=[self.y, -self.y]) 
492        self.right_line.set(xdata=[self.x, self.x], ydata=[self.y, -self.y]) 
493   
494    def save(self, ev):
495        """
496        Remember the roughness for this layer and the next so that we
497        can restore on Esc.
498        """
499        self.save_x = self.x
500        self.save_y = self.y
501        self.base.freeze_axes()
502       
503    def moveend(self, ev):
504        """
505        Called after a dragging this edge and set self.has_move to False
506        to specify the end of dragging motion
507        """
508        self.has_move = False
509        self.base.moveend(ev)
510               
511    def restore(self):
512        """
513        Restore the roughness for this layer.
514        """
515        self.x = self.save_x
516        self.y = self.save_y
517     
518    def move(self, x, y, ev):
519        """
520        Process move to a new position, making sure that the move is allowed.
521        """
522        self.has_move = True
523        self.x = x
524        self.base.base.update()
525       
526   
527class BoxInteractorX(BoxInteractor):
528    """
529    Average in Qx direction
530    """
531    def __init__(self, base, axes, color='black', zorder=3):
532        BoxInteractor.__init__(self, base, axes, color=color)
533        self.base = base
534        self._post_data()
535       
536    def _post_data(self):
537        """
538        Post data creating by averaging in Qx direction
539        """
540        from DataLoader.manipulations import SlabX
541        self.post_data(SlabX, direction="X")   
542       
543
544class BoxInteractorY(BoxInteractor):
545    """
546    Average in Qy direction
547    """
548    def __init__(self, base, axes, color='black', zorder=3):
549        BoxInteractor.__init__(self, base, axes, color=color)
550        self.base = base
551        self._post_data()
552       
553    def _post_data(self):
554        """
555        Post data creating by averaging in Qy direction
556        """
557        from DataLoader.manipulations import SlabY
558        self.post_data(SlabY, direction="Y")   
559       
560       
Note: See TracBrowser for help on using the repository browser.