source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 7e3d422

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 7e3d422 was db7a82e, checked in by Jae Cho <jhjcho@…>, 13 years ago

updated dataloader calls

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