source: sasview/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 0b12abb5

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 0b12abb5 was 83f4445, checked in by Gervaise Alina <gervyh@…>, 15 years ago

working on documentation

  • Property mode set to 100644
File size: 18.4 KB
Line 
1#TODO: the line slicer should listen to all 2DREFRESH events, get the data and slice it
2#      before pushing a new 1D data update.
3
4#
5#TODO: NEED MAJOR REFACTOR
6#
7
8import math
9import wx
10from copy import deepcopy
11# Debug printout
12from sans.guicomm.events import NewPlotEvent, StatusEvent,SlicerParameterEvent,EVT_SLICER_PARS
13from BaseInteractor import _BaseInteractor
14from sans.guiframe.dataFitting import Data1D
15
16class AnnulusInteractor(_BaseInteractor):
17    """
18    Select an annulus through a 2D plot.
19    This interactor is used to average 2D data  with the region
20    defined by 2 radius.
21    this class is defined by 2 Ringinterators.
22    """
23    def __init__(self,base,axes,color='black', zorder=3):
24       
25        _BaseInteractor.__init__(self, base, axes, color=color)
26        self.markers = []
27        self.axes = axes
28        self.base= base
29        self.qmax = min(math.fabs(self.base.data2D.xmax),math.fabs(self.base.data2D.xmin))  #must be positive
30        self.connect = self.base.connect
31   
32        ## Number of points on the plot
33        self.nbins = 20
34       
35        #Cursor position of Rings (Left(-1) or Right(1))
36        self.xmaxd=self.base.data2D.xmax
37        self.xmind=self.base.data2D.xmin
38
39        if (self.xmaxd+self.xmind)>0:
40            self.sign=1
41        else:
42            self.sign=-1
43                 
44        # Inner circle
45        self.inner_circle = RingInteractor(self, self.base.subplot, zorder=zorder, r=self.qmax/2.0,sign=self.sign)
46        self.inner_circle.qmax = self.qmax
47        self.outer_circle = RingInteractor(self, self.base.subplot, zorder=zorder+1, r=self.qmax/1.8,sign=self.sign)
48        self.outer_circle.qmax = self.qmax*1.2
49       
50        self.update()
51        self._post_data()
52       
53        # Bind to slice parameter events
54        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
55       
56    def _onEVT_SLICER_PARS(self, event):
57        """
58        receive an event containing parameters values to reset the slicer
59       
60        :param event: event of type SlicerParameterEvent with params as
61            attribute
62           
63        """
64        wx.PostEvent(self.base, StatusEvent(status="AnnulusSlicer._onEVT_SLICER_PARS"))
65        event.Skip()
66        if event.type == self.__class__.__name__:
67            self.set_params(event.params)
68            self.base.update()
69
70    def set_layer(self, n):
71        """
72        Allow adding plot to the same panel
73       
74        :param n: the number of layer
75       
76        """
77        self.layernum = n
78        self.update()
79       
80    def clear(self):
81        """
82        Clear the slicer and all connected events related to this slicer
83        """
84        self.clear_markers()
85        self.outer_circle.clear()
86        self.inner_circle.clear()
87        self.base.connect.clearall()
88        self.base.Unbind(EVT_SLICER_PARS)
89       
90    def update(self):
91        """
92        Respond to changes in the model by recalculating the profiles and
93        resetting the widgets.
94        """
95        # Update locations       
96        self.inner_circle.update()
97        self.outer_circle.update()
98       
99    def save(self, ev):
100        """
101        Remember the roughness for this layer and the next so that we
102        can restore on Esc.
103        """
104        self.base.freeze_axes()
105        self.inner_circle.save(ev)
106        self.outer_circle.save(ev)
107
108    def _post_data(self,nbins=None):
109        """
110        Uses annulus parameters to plot averaged data into 1D data.
111       
112        :param nbins: the number of points to plot
113       
114        """
115        #Data to average
116        data = self.base.data2D
117        # If we have no data, just return
118        if data == None:
119            return
120       
121        from DataLoader.manipulations import Ring
122   
123        rmin= min(math.fabs(self.inner_circle.get_radius()),
124                  math.fabs(self.outer_circle.get_radius()))
125        rmax = max(math.fabs(self.inner_circle.get_radius()),
126                   math.fabs(self.outer_circle.get_radius()))
127        #if the user does not specify the numbers of points to plot
128        # the default number will be nbins= 20
129        if nbins==None:
130            self.nbins= 20
131        else:
132            self.nbins = nbins
133        ## create the data1D Q average of data2D   
134        sect = Ring(r_min=rmin , r_max= rmax, nbins=self.nbins)
135        sector = sect(self.base.data2D)
136       
137       
138        if hasattr(sector,"dxl"):
139            dxl= sector.dxl
140        else:
141            dxl= None
142        if hasattr(sector,"dxw"):
143            dxw= sector.dxw
144        else:
145            dxw= None
146       
147        new_plot = Data1D(x=(sector.x-math.pi)*180/math.pi,y=sector.y,dy=sector.dy)
148        new_plot.dxl = dxl
149        new_plot.dxw = dxw
150        new_plot.name = "AnnulusPhi" +"("+ self.base.data2D.name+")"
151       
152        new_plot.source=self.base.data2D.source
153        #new_plot.info=self.base.data2D.info
154       
155        new_plot.interactive = True
156        new_plot.detector =self.base.data2D.detector
157        # If the data file does not tell us what the axes are, just assume...
158        new_plot.xaxis("\\rm{\phi}", 'degrees')
159        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
160        new_plot.group_id = "AnnulusPhi"+self.base.data2D.name
161        new_plot.id= "AnnulusPhi"+self.base.data2D.name
162        #new_plot.is_data= True
163       
164        new_plot.xtransform="x"
165        new_plot.ytransform="y"
166        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
167                                                 title="AnnulusPhi" ))
168       
169    def moveend(self, ev):
170        """
171        Called when any dragging motion ends.
172        Post an event (type =SlicerParameterEvent)
173        to plotter 2D with a copy  slicer parameters
174        Call  _post_data method
175        """
176        self.base.thaw_axes()
177        # Post parameters to plotter 2D
178        event = SlicerParameterEvent()
179        event.type = self.__class__.__name__
180        event.params = self.get_params()
181        wx.PostEvent(self.base, event)
182        # create a 1D data plot
183        #self._post_data()
184           
185    def restore(self):
186        """
187        Restore the roughness for this layer.
188        """
189        self.inner_circle.restore()
190        self.outer_circle.restore()
191
192    def move(self, x, y, ev):
193        """
194        Process move to a new position, making sure that the move is allowed.
195        """
196        pass
197       
198    def set_cursor(self, x, y):
199        pass
200       
201    def get_params(self):
202        """
203        Store a copy of values of parameters of the slicer into a dictionary.
204       
205        :return params: the dictionary created
206       
207        """
208        params = {}
209        params["inner_radius"] = math.fabs(self.inner_circle._inner_mouse_x)
210        params["outer_radius"] = math.fabs(self.outer_circle._inner_mouse_x)
211        params["nbins"] = self.nbins
212        return params
213   
214    def set_params(self, params):
215        """
216        Receive a dictionary and reset the slicer with values contained
217        in the values of the dictionary.
218       
219        :param params: a dictionary containing name of slicer parameters and
220            values the user assigned to the slicer.
221           
222        """
223        inner = math.fabs(params["inner_radius"] )
224        outer = math.fabs(params["outer_radius"] )
225        self.nbins = int(params["nbins"])
226        ## Update the picture
227        self.inner_circle.set_cursor(inner, self.inner_circle._inner_mouse_y)
228        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
229        ## Post the data given the nbins entered by the user
230        self._post_data(self.nbins)
231       
232    def freeze_axes(self):
233        """
234        """
235        self.base.freeze_axes()
236       
237    def thaw_axes(self):
238        """
239        """
240        self.base.thaw_axes()
241
242    def draw(self):
243        """
244        """
245        self.base.draw()
246
247       
248class RingInteractor(_BaseInteractor):
249    """
250     Draw a ring Given a radius
251    """
252    def __init__(self,base,axes,color='black', zorder=5, r=1.0,sign=1):
253        """
254        :param: the color of the line that defined the ring
255        :param r: the radius of the ring
256        :param sign: the direction of motion the the marker
257       
258        """
259        _BaseInteractor.__init__(self, base, axes, color=color)
260        self.markers = []
261        self.axes = axes
262        # Current radius of the ring
263        self._inner_mouse_x = r
264        #Value of the center of the ring
265        self._inner_mouse_y = 0
266        # previous value of that radius
267        self._inner_save_x  = r
268        #Save value of the center of the ring
269        self._inner_save_y  = 0
270        #Class instantiating RingIterator class
271        self.base= base
272        #the direction of the motion of the marker
273        self.sign=sign
274        ## Create a marker
275        try:
276            # Inner circle marker
277            self.inner_marker = self.axes.plot([self.sign*math.fabs(self._inner_mouse_x)],[0], linestyle='',
278                                          marker='s', markersize=10,
279                                          color=self.color, alpha=0.6,
280                                          pickradius=5, label="pick", 
281                                          zorder=zorder, # Prefer this to other lines
282                                          visible=True)[0]
283        except:
284            self.inner_marker = self.axes.plot([self.sign*math.fabs(self._inner_mouse_x)],[0], linestyle='',
285                                          marker='s', markersize=10,
286                                          color=self.color, alpha=0.6,
287                                          label="pick", 
288                                          visible=True)[0]
289            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION OF MATPLOTLIB\n"
290            message += "Get the SVN version that is at least as recent as June 1, 2007"
291           
292            owner=self.base.base.parent
293            wx.PostEvent(owner, StatusEvent(status="AnnulusSlicer: %s"%message))
294           
295        # Draw a circle
296        [self.inner_circle] = self.axes.plot([],[],
297                                      linestyle='-', marker='',
298                                      color=self.color)
299        # the number of points that make the ring line
300        self.npts = 40
301           
302        self.connect_markers([self.inner_marker])
303        self.update()
304
305    def set_layer(self, n):
306        """
307        Allow adding plot to the same panel
308         
309        :param n: the number of layer
310       
311        """
312        self.layernum = n
313        self.update()
314       
315    def clear(self):
316        """
317        Clear the slicer and all connected events related to this slicer
318        """
319        self.clear_markers()
320        try:
321            self.inner_marker.remove()
322            self.inner_circle.remove()
323        except:
324            # Old version of matplotlib
325            for item in range(len(self.axes.lines)):
326                del self.axes.lines[0]
327       
328    def get_radius(self):
329        """
330        :return self._inner_mouse_x: the current radius of the ring
331        """
332        return self._inner_mouse_x
333       
334    def update(self):
335        """
336        Draw the new roughness on the graph.
337        """
338        # Plot inner circle
339        x = []
340        y = []
341        for i in range(self.npts):
342            phi = 2.0*math.pi/(self.npts-1)*i
343           
344            xval = 1.0*self._inner_mouse_x*math.cos(phi) 
345            yval = 1.0*self._inner_mouse_x*math.sin(phi) 
346           
347            x.append(xval)
348            y.append(yval)
349           
350        self.inner_marker.set(xdata=[self.sign*math.fabs(self._inner_mouse_x)],ydata=[0])
351        self.inner_circle.set_data(x, y)       
352
353    def save(self, ev):
354        """
355        Remember the roughness for this layer and the next so that we
356        can restore on Esc.
357        """
358        self._inner_save_x = self._inner_mouse_x
359        self._inner_save_y = self._inner_mouse_y
360        self.base.freeze_axes()
361
362    def moveend(self, ev):
363        """
364        Called after a dragging motion
365        """
366        self.base.moveend(ev)
367           
368    def restore(self):
369        """
370        Restore the roughness for this layer.
371        """
372        self._inner_mouse_x = self._inner_save_x
373        self._inner_mouse_y = self._inner_save_y
374
375    def move(self, x, y, ev):
376        """
377        Process move to a new position, making sure that the move is allowed.
378        """
379        self._inner_mouse_x = x
380        self._inner_mouse_y = y
381        self.base.base.update()
382       
383    def set_cursor(self, x, y):
384        """
385        draw the ring given x, y value
386        """
387        self.move(x, y, None)
388        self.update()
389       
390       
391    def get_params(self):
392        """
393        Store a copy of values of parameters of the slicer into a dictionary.
394       
395        :return params: the dictionary created
396       
397        """
398        params = {}
399        params["radius"] = math.fabs(self._inner_mouse_x)
400        return params
401   
402    def set_params(self, params):
403        """
404        Receive a dictionary and reset the slicer with values contained
405        in the values of the dictionary.
406       
407        :param params: a dictionary containing name of slicer parameters and
408            values the user assigned to the slicer.
409           
410        """
411        x = params["radius"] 
412        self.set_cursor(x, self._inner_mouse_y)
413       
414class CircularMask(_BaseInteractor):
415    """
416     Draw a ring Given a radius
417    """
418    def __init__(self,base,axes,color='grey', zorder=3, side=None):
419        """
420       
421        :param: the color of the line that defined the ring
422        :param r: the radius of the ring
423        :param sign: the direction of motion the the marker
424       
425        """
426        _BaseInteractor.__init__(self, base, axes, color=color)
427        self.markers = []
428        self.axes = axes
429        self.base= base
430        self.is_inside = side
431        self.qmax = min(math.fabs(self.base.data.xmax),math.fabs(self.base.data.xmin))  #must be positive
432        self.connect = self.base.connect
433       
434        #Cursor position of Rings (Left(-1) or Right(1))
435        self.xmaxd=self.base.data.xmax
436        self.xmind=self.base.data.xmin
437
438        if (self.xmaxd+self.xmind)>0:
439            self.sign=1
440        else:
441            self.sign=-1
442                 
443        # Inner circle
444        self.outer_circle = RingInteractor(self, self.base.subplot, 'blue', zorder=zorder+1, r=self.qmax/1.8,sign=self.sign)
445        self.outer_circle.qmax = self.qmax*1.2
446       
447        self.update()
448        self._post_data()
449       
450        # Bind to slice parameter events
451        #self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
452       
453    def _onEVT_SLICER_PARS(self, event):
454        """
455        receive an event containing parameters values to reset the slicer
456       
457        :param event: event of type SlicerParameterEvent with params as
458            attribute
459        """
460        wx.PostEvent(self.base, StatusEvent(status="AnnulusSlicer._onEVT_SLICER_PARS"))
461        event.Skip()
462        if event.type == self.__class__.__name__:
463            self.set_params(event.params)
464            self.base.update()
465
466    def set_layer(self, n):
467        """
468        Allow adding plot to the same panel
469         
470        :param n: the number of layer
471       
472        """
473        self.layernum = n
474        self.update()
475       
476    def clear(self):
477        """
478        Clear the slicer and all connected events related to this slicer
479        """
480        self.clear_markers()
481        self.outer_circle.clear()
482        self.base.connect.clearall()
483        #self.base.Unbind(EVT_SLICER_PARS)
484       
485    def update(self):
486        """
487        Respond to changes in the model by recalculating the profiles and
488        resetting the widgets.
489        """
490        # Update locations       
491        self.outer_circle.update()
492        #if self.is_inside != None:
493        out = self._post_data()
494        return out
495
496    def save(self, ev):
497        """
498        Remember the roughness for this layer and the next so that we
499        can restore on Esc.
500        """
501        self.base.freeze_axes()
502        self.outer_circle.save(ev)
503
504    def _post_data(self):
505        """
506        Uses annulus parameters to plot averaged data into 1D data.
507       
508        :param nbins: the number of points to plot
509       
510        """
511        #Data to average
512        data = self.base.data
513             
514        # If we have no data, just return
515        if data == None:
516            return
517        mask = data.mask 
518        from DataLoader.manipulations import Ringcut
519   
520        rmin= 0
521        rmax = math.fabs(self.outer_circle.get_radius())
522
523        ## create the data1D Q average of data2D   
524        mask = Ringcut(r_min=rmin , r_max= rmax)
525
526        if self.is_inside:
527            out = (mask(data)==False)
528        else:
529            out = (mask(data))
530        #self.base.data.mask=out
531        return out                   
532
533         
534    def moveend(self, ev):
535        """
536        Called when any dragging motion ends.
537        Post an event (type =SlicerParameterEvent)
538        to plotter 2D with a copy  slicer parameters
539        Call  _post_data method
540        """
541        self.base.thaw_axes()
542        # create a 1D data plot
543        self._post_data()
544           
545    def restore(self):
546        """
547        Restore the roughness for this layer.
548        """
549        self.outer_circle.restore()
550
551    def move(self, x, y, ev):
552        """
553        Process move to a new position, making sure that the move is allowed.
554        """
555        pass
556       
557    def set_cursor(self, x, y):
558        pass
559       
560    def get_params(self):
561        """
562        Store a copy of values of parameters of the slicer into a dictionary.
563       
564        :return params: the dictionary created
565       
566        """
567        params = {}
568        params["outer_radius"] = math.fabs(self.outer_circle._inner_mouse_x)
569        return params
570   
571    def set_params(self, params):
572        """
573        Receive a dictionary and reset the slicer with values contained
574        in the values of the dictionary.
575       
576        :param params: a dictionary containing name of slicer parameters and
577            values the user assigned to the slicer.
578        """
579        outer = math.fabs(params["outer_radius"] )
580        ## Update the picture
581        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
582        ## Post the data given the nbins entered by the user
583        self._post_data()
584       
585    def freeze_axes(self):
586        self.base.freeze_axes()
587       
588    def thaw_axes(self):
589        self.base.thaw_axes()
590
591    def draw(self):
592        self.base.update()
593             
Note: See TracBrowser for help on using the repository browser.