source: sasview/guiframe/local_perspectives/plotting/SectorSlicer.py @ 343fdb6

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 343fdb6 was d955bf19, checked in by Gervaise Alina <gervyh@…>, 15 years ago

working on documentation

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