source: sasview/guiframe/local_perspectives/plotting/SectorSlicer.py @ 9dd261e

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 9dd261e was 4ac8556, checked in by Gervaise Alina <gervyh@…>, 16 years ago

change on data_loader

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