source: sasview/guiframe/local_perspectives/plotting/SectorSlicer.py @ a7bd562

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 a7bd562 was 70cf5d3, checked in by Gervaise Alina <gervyh@…>, 15 years ago

allow selecting for fit for average data1D

  • 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        new_plot.is_data= True
188        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
189                                                 title="SectorQ"+self.base.data2D.name ))
190       
191         
192       
193    def moveend(self, ev):
194        """
195            Called a dragging motion ends.Get slicer event
196        """
197        self.base.thaw_axes()
198        ## Post parameters
199        event = SlicerParameterEvent()
200        event.type = self.__class__.__name__
201        event.params = self.get_params()
202        ## Send slicer paramers to plotter2D
203        wx.PostEvent(self.base, event)
204        self._post_data()
205           
206           
207    def restore(self):
208        """
209        Restore the roughness for this layer.
210        """
211        self.main_line.restore()
212        self.left_line.restore()
213        self.right_line.restore()
214
215    def move(self, x, y, ev):
216        """
217        Process move to a new position, making sure that the move is allowed.
218        """
219        pass
220       
221       
222    def set_cursor(self, x, y):
223        pass
224       
225       
226    def get_params(self):
227        """
228            Store a copy of values of parameters of the slicer into a dictionary.
229            @return params: the dictionary created
230        """
231        params = {}
232        ## Always make sure that the left and the right line are at phi
233        ## angle of the middle line
234        if math.fabs(self.left_line.phi) != math.fabs(self.right_line.phi):
235            raise ValueError,"Phi left and phi right are different %f, %f"%(self.left_line.phi, self.right_line.phi)
236       
237        params["Phi"] = self.main_line.theta
238        params["Delta_Phi"] = math.fabs(self.left_line.phi)
239        params["nbins"] = self.nbins
240        return params
241   
242   
243    def set_params(self, params):
244        """
245            Receive a dictionary and reset the slicer with values contained
246            in the values of the dictionary.
247            @param params: a dictionary containing name of slicer parameters and
248            values the user assigned to the slicer.
249        """
250        main = params["Phi"] 
251        phi = math.fabs(params["Delta_Phi"] )
252        self.nbins = int(params["nbins"])
253        self.main_line.theta= main
254        ## Reset the slicer parameters
255        self.main_line.update()
256        self.right_line.update( phi=phi,delta=None, mline=self.main_line,
257                               side=True, right=True )
258        self.left_line.update( phi=phi, delta=None, mline=self.main_line, side=True )
259        ## post the new corresponding data
260        self._post_data(nbins=self.nbins)
261       
262       
263    def freeze_axes(self):
264        self.base.freeze_axes()
265       
266       
267    def thaw_axes(self):
268        self.base.thaw_axes()
269
270
271    def draw(self):
272        self.base.draw()
273
274       
275class SideInteractor(_BaseInteractor):
276    """
277        Draw an oblique line
278        @param phi: the phase between the middle line and one side line
279        @param theta2: the angle between the middle line and x- axis
280    """
281    def __init__(self,base,axes,color='black', zorder=5, r=1.0,phi=math.pi/4, theta2= math.pi/3):
282       
283        _BaseInteractor.__init__(self, base, axes, color=color)
284        ## Initialize the class
285        self.markers = []
286        self.axes = axes
287        ## compute the value of the angle between the current line and
288        ## the x-axis 
289        self.save_theta = theta2 + phi
290        self.theta= theta2 + phi
291        ## the value of the middle line angle with respect to the x-axis
292        self.theta2 = theta2
293        ## Radius to find polar coordinates this line's endpoints
294        self.radius = r
295        ## phi is the phase between the current line and the middle line
296        self.phi = phi
297       
298        ## End points polar coordinates
299        x1= self.radius*math.cos(self.theta)
300        y1= self.radius*math.sin(self.theta)
301        x2= -1*self.radius*math.cos(self.theta)
302        y2= -1*self.radius*math.sin(self.theta)
303        ## defining a new marker
304        try:
305            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
306                                          marker='s', markersize=10,
307                                          color=self.color, alpha=0.6,
308                                          pickradius=5, label="pick", 
309                                          zorder=zorder, # Prefer this to other lines
310                                          visible=True)[0]
311        except:
312            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
313                                          marker='s', markersize=10,
314                                          color=self.color, alpha=0.6,
315                                          label="pick", 
316                                          visible=True)[0]
317            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION OF MATPLOTLIB\n"
318            message += "Get the SVN version that is at least as recent as June 1, 2007"
319            owner=self.base.base.parent
320            wx.PostEvent(owner, StatusEvent(status="sectorSlicer: %s"%message))
321       
322        ## Defining the current line
323        self.line = self.axes.plot([x1,x2],[y1,y2],
324                                      linestyle='-', marker='',
325                                      color=self.color,
326                                      visible=True)[0]
327        ## Flag to differentiate the left line from the right line motion
328        self.left_moving=False
329        ## Flag to define a motion
330        self.has_move=False
331        ## connecting markers and draw the picture
332        self.connect_markers([self.inner_marker, self.line])
333       
334
335    def set_layer(self, n):
336        """
337             Allow adding plot to the same panel
338             @param n: the number of layer
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       
357    def update(self, phi=None, delta=None, mline=None,
358               side=False, left= False, right=False):
359        """
360            Draw oblique line
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        #print "update left or right ", self.has_move
365        self.left_moving=left
366        theta3=0
367        if phi !=None:
368            self.phi= phi
369        if delta==None:
370            delta = 0
371
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     
391        x1= self.radius*math.cos(theta3)
392        y1= self.radius*math.sin(theta3)
393        x2= -1*self.radius*math.cos(theta3)
394        y2= -1*self.radius*math.sin(theta3)
395       
396        self.inner_marker.set(xdata=[x1/2.5],ydata=[y1/2.5])
397        self.line.set(xdata=[x1,x2], ydata=[y1,y2]) 
398       
399       
400       
401       
402    def save(self, ev):
403        """
404        Remember the roughness for this layer and the next so that we
405        can restore on Esc.
406        """
407        self.save_theta= self.theta
408        self.base.freeze_axes()
409
410    def moveend(self, ev):
411       
412        self.has_move=False
413        self.base.moveend(ev)
414           
415    def restore(self):
416        """
417        Restore the roughness for this layer.
418        """
419        self.theta = self.save_theta
420
421    def move(self, x, y, ev):
422        """
423        Process move to a new position, making sure that the move is allowed.
424        """
425       
426        self.theta= math.atan2(y,x)
427        self.has_move=True
428        #ToDo: Simplify below
429        if not self.left_moving:
430            if  self.theta2-self.theta <= 0 and self.theta2>0:#>= self.theta2:
431                #print "my theta", self.theta
432                self.restore()
433                return 
434            elif self.theta2 < 0 and self.theta < 0 and self.theta-self.theta2 >= 0:
435                self.restore()
436                return                             
437            elif  self.theta2 < 0 and self.theta > 0 and self.theta2+2*math.pi-self.theta >=math.pi/2:
438                #print "my theta", self.theta
439                self.restore()
440                return 
441            elif  self.theta2 < 0 and self.theta < 0 and self.theta2-self.theta >=math.pi/2:
442                #print "my theta", self.theta
443                self.restore()
444                return 
445            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:
446                #print "self theta encore"
447                self.restore()
448                return 
449        else:
450            #print "left move"
451            if  self.theta < 0 and self.theta+math.pi*2-self.theta2 <= 0:
452                self.restore()
453                return 
454            elif self.theta2 < 0 and self.theta-self.theta2 <= 0:
455                self.restore()
456                return                             
457            elif  self.theta > 0 and self.theta-self.theta2 <=0:
458                #print "my theta", self.theta
459                self.restore()
460                return 
461            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):
462                #print "self theta encore"
463                self.restore()
464                return 
465           
466        self.phi= math.fabs(self.theta2 - self.theta)
467        if self.phi>math.pi:
468            self.phi= 2*math.pi-math.fabs(self.theta2 - self.theta)
469       
470        #print "move , self.phi, self.theta,", self.theta*180/math.pi,self.theta2*180/math.pi,self.phi*180/math.pi
471       
472           
473       
474        self.base.base.update()
475       
476    def set_cursor(self, x, y):
477        self.move(x, y, None)
478        self.update()
479       
480       
481    def get_params(self):
482        params = {}
483        params["radius"] = self.radius
484        params["theta"] = self.theta
485        return params
486   
487    def set_params(self, params):
488
489        x = params["radius"] 
490        self.set_cursor(x, self._inner_mouse_y)
491       
492 
493       
494class LineInteractor(_BaseInteractor):
495    """
496         Select an annulus through a 2D plot
497    """
498    def __init__(self,base,axes,color='black', zorder=5, r=1.0,theta=math.pi/4):
499       
500        _BaseInteractor.__init__(self, base, axes, color=color)
501        self.markers = []
502        self.axes = axes
503       
504        self.save_theta = theta
505        self.theta= theta
506       
507        self.radius = r
508     
509        self.scale = 10.0
510     
511        #raise "Version error", message
512           
513        # Inner circle
514        x1= self.radius*math.cos(self.theta)
515        y1= self.radius*math.sin(self.theta)
516        x2= -1*self.radius*math.cos(self.theta)
517        y2= -1*self.radius*math.sin(self.theta)
518        try:
519            # Inner circle marker
520            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
521                                          marker='s', markersize=10,
522                                          color=self.color, alpha=0.6,
523                                          pickradius=5, label="pick", 
524                                          zorder=zorder, # Prefer this to other lines
525                                          visible=True)[0]
526        except:
527            self.inner_marker = self.axes.plot([x1/2.5],[y1/2.5], linestyle='',
528                                          marker='s', markersize=10,
529                                          color=self.color, alpha=0.6,
530                                          label="pick", 
531                                          visible=True)[0]
532            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION OF MATPLOTLIB\n"
533            message += "Get the SVN version that is at least as recent as June 1, 2007"
534           
535       
536        self.line = self.axes.plot([x1,x2],[y1,y2],
537                                      linestyle='-', marker='',
538                                      color=self.color,
539                                      visible=True)[0]
540     
541        self.npts = 20
542        self.has_move=False
543        self.connect_markers([self.inner_marker, self.line])
544        self.update()
545
546    def set_layer(self, n):
547        self.layernum = n
548        self.update()
549       
550    def clear(self):
551        self.clear_markers()
552        try:
553            self.inner_marker.remove()
554            self.line.remove()
555        except:
556            # Old version of matplotlib
557            for item in range(len(self.axes.lines)):
558                del self.axes.lines[0]
559 
560 
561    def update(self, theta=None):
562        """
563        Draw the new roughness on the graph.
564        """
565       
566        if theta !=None:
567            self.theta= theta
568        x1= self.radius*math.cos(self.theta)
569        y1= self.radius*math.sin(self.theta)
570        x2= -1*self.radius*math.cos(self.theta)
571        y2= -1*self.radius*math.sin(self.theta)
572       
573        self.inner_marker.set(xdata=[x1/2.5],ydata=[y1/2.5])
574        self.line.set(xdata=[x1,x2], ydata=[y1,y2]) 
575     
576       
577       
578    def save(self, ev):
579        """
580        Remember the roughness for this layer and the next so that we
581        can restore on Esc.
582        """
583        self.save_theta= self.theta
584        self.base.freeze_axes()
585
586    def moveend(self, ev):
587       
588        self.has_move=False
589        self.base.moveend(ev)
590           
591    def restore(self):
592        """
593        Restore the roughness for this layer.
594        """
595        self.theta = self.save_theta
596
597    def move(self, x, y, ev):
598        """
599        Process move to a new position, making sure that the move is allowed.
600        """
601       
602        self.theta= math.atan2(y,x)
603        #print "main_line previous theta --- next theta ",math.degrees(self.save_theta),math.degrees(self.theta)
604       
605        self.has_move=True
606       
607        self.base.base.update()
608       
609    def set_cursor(self, x, y):
610        self.move(x, y, None)
611        self.update()
612       
613       
614    def get_params(self):
615        params = {}
616        params["radius"] = self.radius
617        params["theta"] = self.theta
618        return params
619   
620    def set_params(self, params):
621
622        x = params["radius"] 
623        self.set_cursor(x, self._inner_mouse_y)
624
625
626       
Note: See TracBrowser for help on using the repository browser.