source: sasview/guiframe/local_perspectives/plotting/SectorSlicer.py @ 7b73518

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

added comments for delta

  • Property mode set to 100644
File size: 22.1 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
8
9# Debug printout
10import math
[0d9dae8]11import wx
12from copy import deepcopy
13
14from BaseInteractor import _BaseInteractor
[6c0568b]15from sans.guicomm.events import NewPlotEvent, StatusEvent
16from sans.guicomm.events import SlicerParameterEvent,EVT_SLICER_PARS
[ef0c170]17
18
19class SectorInteractor(_BaseInteractor):
20    """
[6c0568b]21         Draw a sector slicer.Allow to performQ averaging on data 2D
[ef0c170]22    """
23    def __init__(self,base,axes,color='black', zorder=3):
24       
25        _BaseInteractor.__init__(self, base, axes, color=color)
[6c0568b]26        ## Class initialization
[ef0c170]27        self.markers = []
[6c0568b]28        self.axes = axes   
29        ## connect the plot to event
[ef0c170]30        self.connect = self.base.connect
31       
[6c0568b]32        ## compute qmax limit to reset the graph     
33        x = math.pow(max(self.base.data2D.xmax,math.fabs(self.base.data2D.xmin)),2)
34        y = math.pow(max(self.base.data2D.ymax,math.fabs(self.base.data2D.ymin)),2)
35        self.qmax= math.sqrt(x + y)
[ef0c170]36        ## Number of points on the plot
[b783024]37        self.nbins = 20
[6c0568b]38        ## Angle of the middle line
[ef0c170]39        self.theta2= math.pi/3
[6c0568b]40        ## Absolute value of the Angle between the middle line and any side line
[ef0c170]41        self.phi=math.pi/12
[6c0568b]42       
43        ## Middle line
44        self.main_line = LineInteractor(self, self.base.subplot,color='blue', zorder=zorder, r=self.qmax,
[ef0c170]45                                           theta= self.theta2)
[2dda74ac]46        self.main_line.qmax = self.qmax
[6c0568b]47        ## Right Side line
[ef0c170]48        self.right_line= SideInteractor(self, self.base.subplot,color='black', zorder=zorder,
49                                     r=self.qmax,
50                                           phi= -1*self.phi,
51                                           theta2=self.theta2)
[2dda74ac]52        self.right_line.qmax = self.qmax
[6c0568b]53        ## Left Side line
54        self.left_line= SideInteractor(self, self.base.subplot,color='black', zorder=zorder,
[ef0c170]55                                     r=self.qmax,
56                                           phi= self.phi,
57                                           theta2=self.theta2)
[2dda74ac]58        self.left_line.qmax = self.qmax
[6c0568b]59        ## draw the sector               
[ef0c170]60        self.update()
61        self._post_data()
[6c0568b]62
63        ## Bind to slice parameter events
[1ce365f8]64        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
[ef0c170]65
[6c0568b]66
[ef0c170]67    def _onEVT_SLICER_PARS(self, event):
[6c0568b]68        """
69            receive an event containing parameters values to reset the slicer
70            @param event: event of type SlicerParameterEvent with params as
71            attribute
72        """
[18eba35]73        wx.PostEvent(self.base.parent, StatusEvent(status="SectorSlicer._onEVT_SLICER_PARS"))
[ef0c170]74        event.Skip()
75        if event.type == self.__class__.__name__:
76            self.set_params(event.params)
77            self.base.update()
78
79
80    def set_layer(self, n):
[6c0568b]81        """
82             Allow adding plot to the same panel
83             @param n: the number of layer
84        """
[ef0c170]85        self.layernum = n
86        self.update()
87       
[6c0568b]88       
[ef0c170]89    def clear(self):
[6c0568b]90        """
91            Clear the slicer and all connected events related to this slicer
92        """
[ef0c170]93        self.clear_markers()
94        self.main_line.clear()
95        self.left_line.clear()
96        self.right_line.clear()
[18eba35]97        self.base.connect.clearall()
[d468daa]98        self.base.Unbind(EVT_SLICER_PARS)
[ef0c170]99       
[6c0568b]100       
[ef0c170]101    def update(self):
102        """
[6c0568b]103            Respond to changes in the model by recalculating the profiles and
104            resetting the widgets.
[ef0c170]105        """
[6c0568b]106        # Update locations 
107        ## Check if the middle line was dragged and update the picture accordingly     
[ef0c170]108        if self.main_line.has_move:
109            self.main_line.update()
[7b73518]110            self.right_line.update( delta= -self.left_line.phi/2,
[6c0568b]111                                    mline= self.main_line.theta )
[7b73518]112            self.left_line.update( delta = self.left_line.phi/2,
[6c0568b]113                                   mline= self.main_line.theta )
114        ## Check if the left side has moved and update the slicer accordingly 
[ef0c170]115        if self.left_line.has_move:
116            self.main_line.update()
[6c0568b]117            self.left_line.update( phi=None, delta=None, mline=self.main_line ,
118                                  side=True, left=True )
119            self.right_line.update( phi= self.left_line.phi, delta= None,
120                                     mline= self.main_line, side= True,
121                                     left=False, right= True )
122        ## Check if the right side line has moved and update the slicer accordingly
[ef0c170]123        if self.right_line.has_move:
124            self.main_line.update()
[6c0568b]125            self.right_line.update( phi=None, delta=None, mline=self.main_line,
126                                   side=True, left=False, right=True )
127            self.left_line.update( phi=self.right_line.phi, delta=None,
128                                    mline=self.main_line, side=True, left=False )
129
[ef0c170]130
131    def save(self, ev):
132        """
133        Remember the roughness for this layer and the next so that we
134        can restore on Esc.
135        """
136        self.base.freeze_axes()
[6c0568b]137        self.main_line.save(ev)
138        self.right_line.save(ev)
139        self.left_line.save(ev)
[ef0c170]140
141    def _post_data(self, nbins=None):
[6c0568b]142        """
143            compute sector averaging of data2D into data1D
144            @param nbins: the number of point to plot for the average 1D data
145        """
146        ## get the data2D to average
[ef0c170]147        data = self.base.data2D
148        # If we have no data, just return
149        if data == None:
150            return
[6c0568b]151        ## Averaging
[ef0c170]152        from DataLoader.manipulations import SectorQ
[6c0568b]153        radius = self.qmax
154        phimin =  -self.left_line.phi + self.main_line.theta
155        phimax = self.left_line.phi + self.main_line.theta
156       
[1ce365f8]157        if nbins==None:
158            nbins = 20
[6c0568b]159        sect = SectorQ(r_min= 0.0, r_max= radius ,
160                        phi_min= phimin + math.pi,
161                        phi_max= phimax + math.pi, nbins=nbins)
162     
[ef0c170]163        sector = sect(self.base.data2D)
[6c0568b]164        ##Create 1D data resulting from average
[ef0c170]165        from sans.guiframe.dataFitting import Data1D
166        if hasattr(sector,"dxl"):
167            dxl= sector.dxl
168        else:
169            dxl= None
170        if hasattr(sector,"dxw"):
171            dxw= sector.dxw
172        else:
173            dxw= None
174       
175        new_plot = Data1D(x=sector.x,y=sector.y,dy=sector.dy,dxl=dxl,dxw=dxw)
176        new_plot.name = "SectorQ" +"("+ self.base.data2D.name+")"
177       
178        new_plot.source=self.base.data2D.source
[8b30e62]179        #new_plot.info=self.base.data2D.info
[ef0c170]180        new_plot.interactive = True
181        new_plot.detector =self.base.data2D.detector
182        # If the data file does not tell us what the axes are, just assume...
[e23a20c]183        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
[ef0c170]184        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
185        new_plot.group_id = "SectorQ"+self.base.data2D.name
[8b30e62]186        new_plot.id = "SectorQ"+self.base.data2D.name
[ef0c170]187        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
[fbd0bece]188                                                 title="SectorQ"+self.base.data2D.name ))
[ef0c170]189       
190         
191       
192    def moveend(self, ev):
[6c0568b]193        """
194            Called a dragging motion ends.Get slicer event
195        """
[ef0c170]196        self.base.thaw_axes()
[6c0568b]197        ## Post parameters
[0d9dae8]198        event = SlicerParameterEvent()
[ef0c170]199        event.type = self.__class__.__name__
200        event.params = self.get_params()
[6c0568b]201        ## Send slicer paramers to plotter2D
[d468daa]202        wx.PostEvent(self.base, event)
[ef0c170]203        self._post_data()
204           
[6c0568b]205           
[ef0c170]206    def restore(self):
207        """
208        Restore the roughness for this layer.
209        """
210        self.main_line.restore()
211        self.left_line.restore()
212        self.right_line.restore()
213
214    def move(self, x, y, ev):
215        """
216        Process move to a new position, making sure that the move is allowed.
217        """
218        pass
219       
[6c0568b]220       
[ef0c170]221    def set_cursor(self, x, y):
222        pass
223       
[6c0568b]224       
[ef0c170]225    def get_params(self):
[6c0568b]226        """
227            Store a copy of values of parameters of the slicer into a dictionary.
228            @return params: the dictionary created
229        """
[ef0c170]230        params = {}
[6c0568b]231        ## Always make sure that the left and the right line are at phi
232        ## angle of the middle line
[ef0c170]233        if math.fabs(self.left_line.phi) != math.fabs(self.right_line.phi):
234            raise ValueError,"Phi left and phi right are different %f, %f"%(self.left_line.phi, self.right_line.phi)
[6c0568b]235       
236        params["Phi"] = self.main_line.theta
[1a6ec25]237        params["Delta_Phi"] = math.fabs(self.left_line.phi)
[ef0c170]238        params["nbins"] = self.nbins
239        return params
240   
[6c0568b]241   
[ef0c170]242    def set_params(self, params):
[6c0568b]243        """
244            Receive a dictionary and reset the slicer with values contained
245            in the values of the dictionary.
246            @param params: a dictionary containing name of slicer parameters and
247            values the user assigned to the slicer.
248        """
[1a6ec25]249        main = params["Phi"] 
250        phi = math.fabs(params["Delta_Phi"] )
[ef0c170]251        self.nbins = int(params["nbins"])
252        self.main_line.theta= main
[6c0568b]253        ## Reset the slicer parameters
[ef0c170]254        self.main_line.update()
[6c0568b]255        self.right_line.update( phi=phi,delta=None, mline=self.main_line,
256                               side=True, right=True )
257        self.left_line.update( phi=phi, delta=None, mline=self.main_line, side=True )
258        ## post the new corresponding data
[ef0c170]259        self._post_data(nbins=self.nbins)
260       
[6c0568b]261       
[ef0c170]262    def freeze_axes(self):
263        self.base.freeze_axes()
264       
[6c0568b]265       
[ef0c170]266    def thaw_axes(self):
267        self.base.thaw_axes()
268
[6c0568b]269
[ef0c170]270    def draw(self):
271        self.base.draw()
272
273       
274class SideInteractor(_BaseInteractor):
275    """
[6c0568b]276        Draw an oblique line
277        @param phi: the phase between the middle line and one side line
278        @param theta2: the angle between the middle line and x- axis
[ef0c170]279    """
280    def __init__(self,base,axes,color='black', zorder=5, r=1.0,phi=math.pi/4, theta2= math.pi/3):
281       
282        _BaseInteractor.__init__(self, base, axes, color=color)
[6c0568b]283        ## Initialize the class
[ef0c170]284        self.markers = []
285        self.axes = axes
[6c0568b]286        ## compute the value of the angle between the current line and
287        ## the x-axis 
[ef0c170]288        self.save_theta = theta2 + phi
[31e3298]289        self.theta= theta2 + phi
[6c0568b]290        ## the value of the middle line angle with respect to the x-axis
[ef0c170]291        self.theta2 = theta2
[6c0568b]292        ## Radius to find polar coordinates this line's endpoints
[ef0c170]293        self.radius = r
[6c0568b]294        ## phi is the phase between the current line and the middle line
[ef0c170]295        self.phi = phi
[6c0568b]296       
297        ## End points polar coordinates
[ef0c170]298        x1= self.radius*math.cos(self.theta)
299        y1= self.radius*math.sin(self.theta)
300        x2= -1*self.radius*math.cos(self.theta)
301        y2= -1*self.radius*math.sin(self.theta)
[6c0568b]302        ## defining a new marker
[7a28ba7]303        try:
[0c218d9]304            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
[7a28ba7]305                                          marker='s', markersize=10,
306                                          color=self.color, alpha=0.6,
307                                          pickradius=5, label="pick", 
308                                          zorder=zorder, # Prefer this to other lines
309                                          visible=True)[0]
310        except:
[0c218d9]311            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
[7a28ba7]312                                          marker='s', markersize=10,
313                                          color=self.color, alpha=0.6,
314                                          label="pick", 
315                                          visible=True)[0]
316            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION OF MATPLOTLIB\n"
317            message += "Get the SVN version that is at least as recent as June 1, 2007"
[6c0568b]318            owner=self.base.base.parent
319            wx.PostEvent(owner, StatusEvent(status="sectorSlicer: %s"%message))
320       
321        ## Defining the current line
[ef0c170]322        self.line = self.axes.plot([x1,x2],[y1,y2],
323                                      linestyle='-', marker='',
324                                      color=self.color,
325                                      visible=True)[0]
[6c0568b]326        ## Flag to differentiate the left line from the right line motion
[7a28ba7]327        self.left_moving=False
[6c0568b]328        ## Flag to define a motion
[ef0c170]329        self.has_move=False
[6c0568b]330        ## connecting markers and draw the picture
[7a28ba7]331        self.connect_markers([self.inner_marker, self.line])
[6c0568b]332       
[ef0c170]333
334    def set_layer(self, n):
[6c0568b]335        """
336             Allow adding plot to the same panel
337             @param n: the number of layer
338        """
[ef0c170]339        self.layernum = n
340        self.update()
341       
342    def clear(self):
[6c0568b]343        """
344            Clear the slicer and all connected events related to this slicer
345        """
[ef0c170]346        self.clear_markers()
347        try:
348            self.line.remove()
[7a28ba7]349            self.inner_marker.remove()
[ef0c170]350        except:
351            # Old version of matplotlib
352            for item in range(len(self.axes.lines)):
353                del self.axes.lines[0]
354       
355       
[6c0568b]356    def update(self, phi=None, delta=None, mline=None,
357               side=False, left= False, right=False):
[ef0c170]358        """
[6c0568b]359            Draw oblique line
360            @param phi: the phase between the middle line and the current line
[7b73518]361            @param delta: phi/2 applied only when the mline was moved
[ef0c170]362        """
363        #print "update left or right ", self.has_move
[7a28ba7]364        self.left_moving=left
[31e3298]365        theta3=0
[ef0c170]366        if phi !=None:
[7a28ba7]367            self.phi= phi
[31e3298]368        if delta==None:
369            delta = 0
370
[7a28ba7]371        if  right:
372            self.phi = -1*math.fabs(self.phi)
[31e3298]373            #delta=-delta
[7a28ba7]374        else:
375            self.phi =math.fabs(self.phi)
[e23a20c]376        if side:
[ef0c170]377            self.theta=  mline.theta + self.phi
[31e3298]378                   
379        if mline!=None :
380            if delta!=0:
381                self.theta2 = mline+delta
382            else:
383                self.theta2 = mline.theta
384        if delta==0:
385            theta3=self.theta+delta
386        else:
387            theta3=self.theta2+delta
[7a28ba7]388       
[6c0568b]389     
[31e3298]390        x1= self.radius*math.cos(theta3)
391        y1= self.radius*math.sin(theta3)
392        x2= -1*self.radius*math.cos(theta3)
393        y2= -1*self.radius*math.sin(theta3)
[ef0c170]394       
[0c218d9]395        self.inner_marker.set(xdata=[x1/2.5],ydata=[y1/2.5])
[ef0c170]396        self.line.set(xdata=[x1,x2], ydata=[y1,y2]) 
397       
398       
399       
400       
401    def save(self, ev):
402        """
403        Remember the roughness for this layer and the next so that we
404        can restore on Esc.
405        """
406        self.save_theta= self.theta
407        self.base.freeze_axes()
408
409    def moveend(self, ev):
410       
411        self.has_move=False
412        self.base.moveend(ev)
413           
414    def restore(self):
415        """
416        Restore the roughness for this layer.
417        """
418        self.theta = self.save_theta
419
420    def move(self, x, y, ev):
421        """
422        Process move to a new position, making sure that the move is allowed.
423        """
424       
425        self.theta= math.atan2(y,x)
[7a28ba7]426        self.has_move=True
[0c218d9]427        #ToDo: Simplify below
[7a28ba7]428        if not self.left_moving:
[0c218d9]429            if  self.theta2-self.theta <= 0 and self.theta2>0:#>= self.theta2:
430                #print "my theta", self.theta
431                self.restore()
432                return 
433            elif self.theta2 < 0 and self.theta < 0 and self.theta-self.theta2 >= 0:
434                self.restore()
435                return                             
436            elif  self.theta2 < 0 and self.theta > 0 and self.theta2+2*math.pi-self.theta >=math.pi/2:
437                #print "my theta", self.theta
438                self.restore()
439                return 
440            elif  self.theta2 < 0 and self.theta < 0 and self.theta2-self.theta >=math.pi/2:
441                #print "my theta", self.theta
[7a28ba7]442                self.restore()
443                return 
[0c218d9]444            elif self.theta2>0 and (self.theta2-self.theta >= math.pi/2 or (self.theta2-self.theta >= math.pi/2)):#<= self.theta2 -math.pi/2:
445                #print "self theta encore"
[7a28ba7]446                self.restore()
447                return 
448        else:
[0c218d9]449            #print "left move"
450            if  self.theta < 0 and self.theta+math.pi*2-self.theta2 <= 0:
[7a28ba7]451                self.restore()
452                return 
[0c218d9]453            elif self.theta2 < 0 and self.theta-self.theta2 <= 0:
454                self.restore()
455                return                             
456            elif  self.theta > 0 and self.theta-self.theta2 <=0:
457                #print "my theta", self.theta
458                self.restore()
459                return 
460            elif self.theta-self.theta2 >= math.pi/2 or  (self.theta+math.pi*2-self.theta2 >= math.pi/2 and self.theta<0 and self.theta2>0):
461                #print "self theta encore"
[7a28ba7]462                self.restore()
463                return 
[0c218d9]464           
[e23a20c]465        self.phi= math.fabs(self.theta2 - self.theta)
[0c218d9]466        if self.phi>math.pi:
467            self.phi= 2*math.pi-math.fabs(self.theta2 - self.theta)
[ef0c170]468       
[0c218d9]469        #print "move , self.phi, self.theta,", self.theta*180/math.pi,self.theta2*180/math.pi,self.phi*180/math.pi
[7a28ba7]470       
471           
472       
[ef0c170]473        self.base.base.update()
474       
475    def set_cursor(self, x, y):
476        self.move(x, y, None)
477        self.update()
478       
479       
480    def get_params(self):
481        params = {}
482        params["radius"] = self.radius
483        params["theta"] = self.theta
484        return params
485   
486    def set_params(self, params):
487
488        x = params["radius"] 
489        self.set_cursor(x, self._inner_mouse_y)
490       
[7a28ba7]491 
[ef0c170]492       
493class LineInteractor(_BaseInteractor):
494    """
495         Select an annulus through a 2D plot
496    """
497    def __init__(self,base,axes,color='black', zorder=5, r=1.0,theta=math.pi/4):
498       
499        _BaseInteractor.__init__(self, base, axes, color=color)
500        self.markers = []
501        self.axes = axes
502       
503        self.save_theta = theta
504        self.theta= theta
505       
506        self.radius = r
507     
508        self.scale = 10.0
509     
510        #raise "Version error", message
511           
512        # Inner circle
513        x1= self.radius*math.cos(self.theta)
514        y1= self.radius*math.sin(self.theta)
515        x2= -1*self.radius*math.cos(self.theta)
516        y2= -1*self.radius*math.sin(self.theta)
[7a28ba7]517        try:
518            # Inner circle marker
[0c218d9]519            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
[7a28ba7]520                                          marker='s', markersize=10,
521                                          color=self.color, alpha=0.6,
522                                          pickradius=5, label="pick", 
523                                          zorder=zorder, # Prefer this to other lines
524                                          visible=True)[0]
525        except:
[0c218d9]526            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
[7a28ba7]527                                          marker='s', markersize=10,
528                                          color=self.color, alpha=0.6,
529                                          label="pick", 
530                                          visible=True)[0]
531            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION OF MATPLOTLIB\n"
532            message += "Get the SVN version that is at least as recent as June 1, 2007"
533           
[ef0c170]534       
535        self.line = self.axes.plot([x1,x2],[y1,y2],
536                                      linestyle='-', marker='',
537                                      color=self.color,
538                                      visible=True)[0]
539     
540        self.npts = 20
541        self.has_move=False
[7a28ba7]542        self.connect_markers([self.inner_marker, self.line])
[ef0c170]543        self.update()
544
545    def set_layer(self, n):
546        self.layernum = n
547        self.update()
548       
549    def clear(self):
550        self.clear_markers()
551        try:
[7a28ba7]552            self.inner_marker.remove()
[ef0c170]553            self.line.remove()
554        except:
555            # Old version of matplotlib
556            for item in range(len(self.axes.lines)):
557                del self.axes.lines[0]
[6c0568b]558 
559 
[ef0c170]560    def update(self, theta=None):
561        """
562        Draw the new roughness on the graph.
563        """
[6c0568b]564       
[ef0c170]565        if theta !=None:
566            self.theta= theta
567        x1= self.radius*math.cos(self.theta)
568        y1= self.radius*math.sin(self.theta)
569        x2= -1*self.radius*math.cos(self.theta)
570        y2= -1*self.radius*math.sin(self.theta)
[7a28ba7]571       
[0c218d9]572        self.inner_marker.set(xdata=[x1/2.5],ydata=[y1/2.5])
[ef0c170]573        self.line.set(xdata=[x1,x2], ydata=[y1,y2]) 
574     
575       
576       
577    def save(self, ev):
578        """
579        Remember the roughness for this layer and the next so that we
580        can restore on Esc.
581        """
582        self.save_theta= self.theta
583        self.base.freeze_axes()
584
585    def moveend(self, ev):
586       
587        self.has_move=False
588        self.base.moveend(ev)
589           
590    def restore(self):
591        """
592        Restore the roughness for this layer.
593        """
594        self.theta = self.save_theta
595
596    def move(self, x, y, ev):
597        """
598        Process move to a new position, making sure that the move is allowed.
599        """
600       
601        self.theta= math.atan2(y,x)
[0c218d9]602        #print "main_line previous theta --- next theta ",math.degrees(self.save_theta),math.degrees(self.theta)
[ef0c170]603       
604        self.has_move=True
[0c218d9]605       
[ef0c170]606        self.base.base.update()
607       
608    def set_cursor(self, x, y):
609        self.move(x, y, None)
610        self.update()
611       
612       
613    def get_params(self):
614        params = {}
615        params["radius"] = self.radius
616        params["theta"] = self.theta
617        return params
618   
619    def set_params(self, params):
620
621        x = params["radius"] 
622        self.set_cursor(x, self._inner_mouse_y)
623
624
[356aea78]625       
Note: See TracBrowser for help on using the repository browser.