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
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
8
9# Debug printout
10import math
11import wx
12from copy import deepcopy
13
14from BaseInteractor import _BaseInteractor
15from sans.guicomm.events import NewPlotEvent, StatusEvent
16from sans.guicomm.events import SlicerParameterEvent,EVT_SLICER_PARS
17
18
19class SectorInteractor(_BaseInteractor):
20    """
21         Draw a sector slicer.Allow to performQ averaging on data 2D
22    """
23    def __init__(self,base,axes,color='black', zorder=3):
24       
25        _BaseInteractor.__init__(self, base, axes, color=color)
26        ## Class initialization
27        self.markers = []
28        self.axes = axes   
29        ## connect the plot to event
30        self.connect = self.base.connect
31       
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)
36        ## Number of points on the plot
37        self.nbins = 20
38        ## Angle of the middle line
39        self.theta2= math.pi/3
40        ## Absolute value of the Angle between the middle line and any side line
41        self.phi=math.pi/12
42       
43        ## Middle line
44        self.main_line = LineInteractor(self, self.base.subplot,color='blue', zorder=zorder, r=self.qmax,
45                                           theta= self.theta2)
46        self.main_line.qmax = self.qmax
47        ## Right Side line
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)
52        self.right_line.qmax = self.qmax
53        ## Left Side line
54        self.left_line= SideInteractor(self, self.base.subplot,color='black', zorder=zorder,
55                                     r=self.qmax,
56                                           phi= self.phi,
57                                           theta2=self.theta2)
58        self.left_line.qmax = self.qmax
59        ## draw the sector               
60        self.update()
61        self._post_data()
62
63        ## Bind to slice parameter events
64        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
65
66
67    def _onEVT_SLICER_PARS(self, event):
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        """
73        wx.PostEvent(self.base.parent, StatusEvent(status="SectorSlicer._onEVT_SLICER_PARS"))
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):
81        """
82             Allow adding plot to the same panel
83             @param n: the number of layer
84        """
85        self.layernum = n
86        self.update()
87       
88       
89    def clear(self):
90        """
91            Clear the slicer and all connected events related to this slicer
92        """
93        self.clear_markers()
94        self.main_line.clear()
95        self.left_line.clear()
96        self.right_line.clear()
97        self.base.connect.clearall()
98        self.base.Unbind(EVT_SLICER_PARS)
99       
100       
101    def update(self):
102        """
103            Respond to changes in the model by recalculating the profiles and
104            resetting the widgets.
105        """
106        # Update locations 
107        ## Check if the middle line was dragged and update the picture accordingly     
108        if self.main_line.has_move:
109            self.main_line.update()
110            self.right_line.update( delta= -self.left_line.phi/2,
111                                    mline= self.main_line.theta )
112            self.left_line.update( delta = self.left_line.phi/2,
113                                   mline= self.main_line.theta )
114        ## Check if the left side has moved and update the slicer accordingly 
115        if self.left_line.has_move:
116            self.main_line.update()
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
123        if self.right_line.has_move:
124            self.main_line.update()
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
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()
137        self.main_line.save(ev)
138        self.right_line.save(ev)
139        self.left_line.save(ev)
140
141    def _post_data(self, nbins=None):
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
147        data = self.base.data2D
148        # If we have no data, just return
149        if data == None:
150            return
151        ## Averaging
152        from DataLoader.manipulations import SectorQ
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       
157        if nbins==None:
158            nbins = 20
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     
163        sector = sect(self.base.data2D)
164        ##Create 1D data resulting from average
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
179        #new_plot.info=self.base.data2D.info
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...
183        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
184        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
185        new_plot.group_id = "SectorQ"+self.base.data2D.name
186        new_plot.id = "SectorQ"+self.base.data2D.name
187        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
188                                                 title="SectorQ"+self.base.data2D.name ))
189       
190         
191       
192    def moveend(self, ev):
193        """
194            Called a dragging motion ends.Get slicer event
195        """
196        self.base.thaw_axes()
197        ## Post parameters
198        event = SlicerParameterEvent()
199        event.type = self.__class__.__name__
200        event.params = self.get_params()
201        ## Send slicer paramers to plotter2D
202        wx.PostEvent(self.base, event)
203        self._post_data()
204           
205           
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       
220       
221    def set_cursor(self, x, y):
222        pass
223       
224       
225    def get_params(self):
226        """
227            Store a copy of values of parameters of the slicer into a dictionary.
228            @return params: the dictionary created
229        """
230        params = {}
231        ## Always make sure that the left and the right line are at phi
232        ## angle of the middle line
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)
235       
236        params["Phi"] = self.main_line.theta
237        params["Delta_Phi"] = math.fabs(self.left_line.phi)
238        params["nbins"] = self.nbins
239        return params
240   
241   
242    def set_params(self, params):
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        """
249        main = params["Phi"] 
250        phi = math.fabs(params["Delta_Phi"] )
251        self.nbins = int(params["nbins"])
252        self.main_line.theta= main
253        ## Reset the slicer parameters
254        self.main_line.update()
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
259        self._post_data(nbins=self.nbins)
260       
261       
262    def freeze_axes(self):
263        self.base.freeze_axes()
264       
265       
266    def thaw_axes(self):
267        self.base.thaw_axes()
268
269
270    def draw(self):
271        self.base.draw()
272
273       
274class SideInteractor(_BaseInteractor):
275    """
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
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)
283        ## Initialize the class
284        self.markers = []
285        self.axes = axes
286        ## compute the value of the angle between the current line and
287        ## the x-axis 
288        self.save_theta = theta2 + phi
289        self.theta= theta2 + phi
290        ## the value of the middle line angle with respect to the x-axis
291        self.theta2 = theta2
292        ## Radius to find polar coordinates this line's endpoints
293        self.radius = r
294        ## phi is the phase between the current line and the middle line
295        self.phi = phi
296       
297        ## End points polar coordinates
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)
302        ## defining a new marker
303        try:
304            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
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:
311            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
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"
318            owner=self.base.base.parent
319            wx.PostEvent(owner, StatusEvent(status="sectorSlicer: %s"%message))
320       
321        ## Defining the current line
322        self.line = self.axes.plot([x1,x2],[y1,y2],
323                                      linestyle='-', marker='',
324                                      color=self.color,
325                                      visible=True)[0]
326        ## Flag to differentiate the left line from the right line motion
327        self.left_moving=False
328        ## Flag to define a motion
329        self.has_move=False
330        ## connecting markers and draw the picture
331        self.connect_markers([self.inner_marker, self.line])
332       
333
334    def set_layer(self, n):
335        """
336             Allow adding plot to the same panel
337             @param n: the number of layer
338        """
339        self.layernum = n
340        self.update()
341       
342    def clear(self):
343        """
344            Clear the slicer and all connected events related to this slicer
345        """
346        self.clear_markers()
347        try:
348            self.line.remove()
349            self.inner_marker.remove()
350        except:
351            # Old version of matplotlib
352            for item in range(len(self.axes.lines)):
353                del self.axes.lines[0]
354       
355       
356    def update(self, phi=None, delta=None, mline=None,
357               side=False, left= False, right=False):
358        """
359            Draw oblique line
360            @param phi: the phase between the middle line and the current line
361            @param delta: phi/2 applied only when the mline was moved
362        """
363        #print "update left or right ", self.has_move
364        self.left_moving=left
365        theta3=0
366        if phi !=None:
367            self.phi= phi
368        if delta==None:
369            delta = 0
370
371        if  right:
372            self.phi = -1*math.fabs(self.phi)
373            #delta=-delta
374        else:
375            self.phi =math.fabs(self.phi)
376        if side:
377            self.theta=  mline.theta + self.phi
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
388       
389     
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)
394       
395        self.inner_marker.set(xdata=[x1/2.5],ydata=[y1/2.5])
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)
426        self.has_move=True
427        #ToDo: Simplify below
428        if not self.left_moving:
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
442                self.restore()
443                return 
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"
446                self.restore()
447                return 
448        else:
449            #print "left move"
450            if  self.theta < 0 and self.theta+math.pi*2-self.theta2 <= 0:
451                self.restore()
452                return 
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"
462                self.restore()
463                return 
464           
465        self.phi= math.fabs(self.theta2 - self.theta)
466        if self.phi>math.pi:
467            self.phi= 2*math.pi-math.fabs(self.theta2 - self.theta)
468       
469        #print "move , self.phi, self.theta,", self.theta*180/math.pi,self.theta2*180/math.pi,self.phi*180/math.pi
470       
471           
472       
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       
491 
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)
517        try:
518            # Inner circle marker
519            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
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:
526            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
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           
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
542        self.connect_markers([self.inner_marker, self.line])
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:
552            self.inner_marker.remove()
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]
558 
559 
560    def update(self, theta=None):
561        """
562        Draw the new roughness on the graph.
563        """
564       
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)
571       
572        self.inner_marker.set(xdata=[x1/2.5],ydata=[y1/2.5])
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)
602        #print "main_line previous theta --- next theta ",math.degrees(self.save_theta),math.degrees(self.theta)
603       
604        self.has_move=True
605       
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
625       
Note: See TracBrowser for help on using the repository browser.