source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 0203ade

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 0203ade was 940aca7, checked in by Mathieu Doucet <doucetm@…>, 13 years ago

Merge 2.1.1 into trunk

  • Property mode set to 100644
File size: 19.5 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 sans.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        if hasattr(data, "scale") and data.scale == 'linear' and \
164                self.base.data2D.name.count("Residuals") > 0:
165            new_plot.ytransform = 'y'
166            new_plot.yaxis("\\rm{Residuals} ", "/")
167
168        new_plot.group_id = "AnnulusPhi" + self.base.data2D.name
169        new_plot.id = "AnnulusPhi" + self.base.data2D.name
170        new_plot.is_data= True
171        new_plot.xtransform = "x"
172        new_plot.ytransform = "y"
173        self.base.parent.update_theory(data_id=data.id, theory=new_plot)
174        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
175                                                 title="AnnulusPhi"))
176       
177    def moveend(self, ev):
178        """
179        Called when any dragging motion ends.
180        Post an event (type =SlicerParameterEvent)
181        to plotter 2D with a copy  slicer parameters
182        Call  _post_data method
183        """
184        self.base.thaw_axes()
185        # Post parameters to plotter 2D
186        event = SlicerParameterEvent()
187        event.type = self.__class__.__name__
188        event.params = self.get_params()
189        wx.PostEvent(self.base, event)
190        # create a 1D data plot
191        #self._post_data()
192           
193    def restore(self):
194        """
195        Restore the roughness for this layer.
196        """
197        self.inner_circle.restore()
198        self.outer_circle.restore()
199
200    def move(self, x, y, ev):
201        """
202        Process move to a new position, making sure that the move is allowed.
203        """
204        pass
205       
206    def set_cursor(self, x, y):
207        pass
208       
209    def get_params(self):
210        """
211        Store a copy of values of parameters of the slicer into a dictionary.
212       
213        :return params: the dictionary created
214       
215        """
216        params = {}
217        params["inner_radius"] = math.fabs(self.inner_circle._inner_mouse_x)
218        params["outer_radius"] = math.fabs(self.outer_circle._inner_mouse_x)
219        params["nbins"] = self.nbins
220        return params
221   
222    def set_params(self, params):
223        """
224        Receive a dictionary and reset the slicer with values contained
225        in the values of the dictionary.
226       
227        :param params: a dictionary containing name of slicer parameters and
228            values the user assigned to the slicer.
229           
230        """
231        inner = math.fabs(params["inner_radius"])
232        outer = math.fabs(params["outer_radius"])
233        self.nbins = int(params["nbins"])
234        ## Update the picture
235        self.inner_circle.set_cursor(inner, self.inner_circle._inner_mouse_y)
236        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
237        ## Post the data given the nbins entered by the user
238        self._post_data(self.nbins)
239       
240    def freeze_axes(self):
241        """
242        """
243        self.base.freeze_axes()
244       
245    def thaw_axes(self):
246        """
247        """
248        self.base.thaw_axes()
249
250    def draw(self):
251        """
252        """
253        self.base.draw()
254
255       
256class RingInteractor(_BaseInteractor):
257    """
258     Draw a ring Given a radius
259    """
260    def __init__(self, base, axes, color='black', zorder=5, r=1.0, sign=1):
261        """
262        :param: the color of the line that defined the ring
263        :param r: the radius of the ring
264        :param sign: the direction of motion the the marker
265       
266        """
267        _BaseInteractor.__init__(self, base, axes, color=color)
268        self.markers = []
269        self.axes = axes
270        # Current radius of the ring
271        self._inner_mouse_x = r
272        #Value of the center of the ring
273        self._inner_mouse_y = 0
274        # previous value of that radius
275        self._inner_save_x  = r
276        #Save value of the center of the ring
277        self._inner_save_y  = 0
278        #Class instantiating RingIterator class
279        self.base = base
280        #the direction of the motion of the marker
281        self.sign = sign
282        ## Create a marker
283        try:
284            # Inner circle marker
285            x_value = [self.sign * math.fabs(self._inner_mouse_x)]
286            self.inner_marker = self.axes.plot(x_value,
287                                               [0],
288                                                linestyle='',
289                                          marker='s', markersize=10,
290                                          color=self.color, alpha=0.6,
291                                          pickradius=5, label="pick", 
292                                          zorder=zorder,
293                                          visible=True)[0]
294        except:
295            x_value = [self.sign * math.fabs(self._inner_mouse_x)]
296            self.inner_marker = self.axes.plot(x_value,
297                                               [0], 
298                                               linestyle='',
299                                          marker='s', markersize=10,
300                                          color=self.color, alpha=0.6,
301                                          label="pick", 
302                                          visible=True)[0]
303            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST"
304            message += " VERSION OF MATPLOTLIB\n"
305            message += "Get the SVN version that is at "
306            message += " least as recent as June 1, 2007"
307           
308            owner = self.base.base.parent
309            wx.PostEvent(owner, 
310                         StatusEvent(status="AnnulusSlicer: %s" % message))
311           
312        # Draw a circle
313        [self.inner_circle] = self.axes.plot([], [],
314                                      linestyle='-', marker='',
315                                      color=self.color)
316        # the number of points that make the ring line
317        self.npts = 40
318           
319        self.connect_markers([self.inner_marker])
320        self.update()
321
322    def set_layer(self, n):
323        """
324        Allow adding plot to the same panel
325         
326        :param n: the number of layer
327       
328        """
329        self.layernum = n
330        self.update()
331       
332    def clear(self):
333        """
334        Clear the slicer and all connected events related to this slicer
335        """
336        self.clear_markers()
337        try:
338            self.inner_marker.remove()
339            self.inner_circle.remove()
340        except:
341            # Old version of matplotlib
342            for item in range(len(self.axes.lines)):
343                del self.axes.lines[0]
344       
345    def get_radius(self):
346        """
347        :return self._inner_mouse_x: the current radius of the ring
348        """
349        return self._inner_mouse_x
350       
351    def update(self):
352        """
353        Draw the new roughness on the graph.
354        """
355        # Plot inner circle
356        x = []
357        y = []
358        for i in range(self.npts):
359            phi = 2.0 * math.pi / (self.npts - 1) * i
360           
361            xval = 1.0 * self._inner_mouse_x * math.cos(phi) 
362            yval = 1.0 * self._inner_mouse_x * math.sin(phi) 
363           
364            x.append(xval)
365            y.append(yval)
366           
367        self.inner_marker.set(xdata=[self.sign*math.fabs(self._inner_mouse_x)],
368                              ydata=[0])
369        self.inner_circle.set_data(x, y)       
370
371    def save(self, ev):
372        """
373        Remember the roughness for this layer and the next so that we
374        can restore on Esc.
375        """
376        self._inner_save_x = self._inner_mouse_x
377        self._inner_save_y = self._inner_mouse_y
378        self.base.freeze_axes()
379
380    def moveend(self, ev):
381        """
382        Called after a dragging motion
383        """
384        self.base.moveend(ev)
385           
386    def restore(self):
387        """
388        Restore the roughness for this layer.
389        """
390        self._inner_mouse_x = self._inner_save_x
391        self._inner_mouse_y = self._inner_save_y
392
393    def move(self, x, y, ev):
394        """
395        Process move to a new position, making sure that the move is allowed.
396        """
397        self._inner_mouse_x = x
398        self._inner_mouse_y = y
399        self.base.base.update()
400       
401    def set_cursor(self, x, y):
402        """
403        draw the ring given x, y value
404        """
405        self.move(x, y, None)
406        self.update()
407       
408       
409    def get_params(self):
410        """
411        Store a copy of values of parameters of the slicer into a dictionary.
412       
413        :return params: the dictionary created
414       
415        """
416        params = {}
417        params["radius"] = math.fabs(self._inner_mouse_x)
418        return params
419   
420    def set_params(self, params):
421        """
422        Receive a dictionary and reset the slicer with values contained
423        in the values of the dictionary.
424       
425        :param params: a dictionary containing name of slicer parameters and
426            values the user assigned to the slicer.
427           
428        """
429        x = params["radius"] 
430        self.set_cursor(x, self._inner_mouse_y)
431       
432class CircularMask(_BaseInteractor):
433    """
434     Draw a ring Given a radius
435    """
436    def __init__(self, base, axes, color='grey', zorder=3, side=None):
437        """
438       
439        :param: the color of the line that defined the ring
440        :param r: the radius of the ring
441        :param sign: the direction of motion the the marker
442       
443        """
444        _BaseInteractor.__init__(self, base, axes, color=color)
445        self.markers = []
446        self.axes = axes
447        self.base = base
448        self.is_inside = side
449        self.qmax = min(math.fabs(self.base.data.xmax),
450                        math.fabs(self.base.data.xmin))  #must be positive
451        self.connect = self.base.connect
452       
453        #Cursor position of Rings (Left(-1) or Right(1))
454        self.xmaxd = self.base.data.xmax
455        self.xmind = self.base.data.xmin
456
457        if (self.xmaxd + self.xmind) > 0:
458            self.sign = 1
459        else:
460            self.sign = -1
461        # Inner circle
462        self.outer_circle = RingInteractor(self, self.base.subplot, 'blue',
463                                            zorder=zorder+1, r=self.qmax/1.8,
464                                            sign=self.sign)
465        self.outer_circle.qmax = self.qmax * 1.2
466        self.update()
467        self._post_data()
468       
469        # Bind to slice parameter events
470        #self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
471       
472    def _onEVT_SLICER_PARS(self, event):
473        """
474        receive an event containing parameters values to reset the slicer
475       
476        :param event: event of type SlicerParameterEvent with params as
477            attribute
478        """
479        wx.PostEvent(self.base,
480                     StatusEvent(status="AnnulusSlicer._onEVT_SLICER_PARS"))
481        event.Skip()
482        if event.type == self.__class__.__name__:
483            self.set_params(event.params)
484            self.base.update()
485
486    def set_layer(self, n):
487        """
488        Allow adding plot to the same panel
489         
490        :param n: the number of layer
491       
492        """
493        self.layernum = n
494        self.update()
495       
496    def clear(self):
497        """
498        Clear the slicer and all connected events related to this slicer
499        """
500        self.clear_markers()
501        self.outer_circle.clear()
502        self.base.connect.clearall()
503        #self.base.Unbind(EVT_SLICER_PARS)
504       
505    def update(self):
506        """
507        Respond to changes in the model by recalculating the profiles and
508        resetting the widgets.
509        """
510        # Update locations       
511        self.outer_circle.update()
512        #if self.is_inside != None:
513        out = self._post_data()
514        return out
515
516    def save(self, ev):
517        """
518        Remember the roughness for this layer and the next so that we
519        can restore on Esc.
520        """
521        self.base.freeze_axes()
522        self.outer_circle.save(ev)
523
524    def _post_data(self):
525        """
526        Uses annulus parameters to plot averaged data into 1D data.
527       
528        :param nbins: the number of points to plot
529       
530        """
531        #Data to average
532        data = self.base.data
533             
534        # If we have no data, just return
535        if data == None:
536            return
537        mask = data.mask 
538        from sans.dataloader.manipulations import Ringcut
539   
540        rmin = 0
541        rmax = math.fabs(self.outer_circle.get_radius())
542
543        ## create the data1D Q average of data2D   
544        mask = Ringcut(r_min=rmin, r_max= rmax)
545
546        if self.is_inside:
547            out = (mask(data) == False)
548        else:
549            out = (mask(data))
550        #self.base.data.mask=out
551        return out                   
552
553         
554    def moveend(self, ev):
555        """
556        Called when any dragging motion ends.
557        Post an event (type =SlicerParameterEvent)
558        to plotter 2D with a copy  slicer parameters
559        Call  _post_data method
560        """
561        self.base.thaw_axes()
562        # create a 1D data plot
563        self._post_data()
564           
565    def restore(self):
566        """
567        Restore the roughness for this layer.
568        """
569        self.outer_circle.restore()
570
571    def move(self, x, y, ev):
572        """
573        Process move to a new position, making sure that the move is allowed.
574        """
575        pass
576       
577    def set_cursor(self, x, y):
578        pass
579       
580    def get_params(self):
581        """
582        Store a copy of values of parameters of the slicer into a dictionary.
583       
584        :return params: the dictionary created
585       
586        """
587        params = {}
588        params["outer_radius"] = math.fabs(self.outer_circle._inner_mouse_x)
589        return params
590   
591    def set_params(self, params):
592        """
593        Receive a dictionary and reset the slicer with values contained
594        in the values of the dictionary.
595       
596        :param params: a dictionary containing name of slicer parameters and
597            values the user assigned to the slicer.
598        """
599        outer = math.fabs(params["outer_radius"] )
600        ## Update the picture
601        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
602        ## Post the data given the nbins entered by the user
603        self._post_data()
604       
605    def freeze_axes(self):
606        self.base.freeze_axes()
607       
608    def thaw_axes(self):
609        self.base.thaw_axes()
610
611    def draw(self):
612        self.base.update()
613             
Note: See TracBrowser for help on using the repository browser.