source: sasview/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 3a848b2

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 3a848b2 was aef2cf2, checked in by Jae Cho <jhjcho@…>, 15 years ago

changed the color of slicer for masking 'cause not visuable in the masked region

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