source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/SectorSlicer.py @ ae84427

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 ae84427 was 657e52c, checked in by Jae Cho <jhjcho@…>, 12 years ago

merging from the release 2.2.0

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