source: sasview/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ fcc5680

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 fcc5680 was 55a0dc1, checked in by Gervaise Alina <gervyh@…>, 14 years ago

remove reference to guicomm in guiframe

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