source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 2d33e76

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 2d33e76 was 8c347a6, checked in by Gervaise Alina <gervyh@…>, 13 years ago

moving guiframe under sansguiframe

  • Property mode set to 100644
File size: 19.4 KB
RevLine 
[ef0c170]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
[0d9dae8]8import math
9import wx
[32c0841]10#from copy import deepcopy
[ef0c170]11# Debug printout
[55a0dc1]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
[ef0c170]16from BaseInteractor import _BaseInteractor
[4ac8556]17from sans.guiframe.dataFitting import Data1D
[ef0c170]18
19class AnnulusInteractor(_BaseInteractor):
20    """
[83f4445]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.
[ef0c170]25    """
[32c0841]26    def __init__(self, base, axes, color='black', zorder=3):
[ef0c170]27       
28        _BaseInteractor.__init__(self, base, axes, color=color)
29        self.markers = []
30        self.axes = axes
[32c0841]31        self.base = base
32        self.qmax = min(math.fabs(self.base.data2D.xmax),
33                        math.fabs(self.base.data2D.xmin))  #must be positive
[ef0c170]34        self.connect = self.base.connect
[eba08f1a]35   
[ef0c170]36        ## Number of points on the plot
37        self.nbins = 20
[a2c38de]38        #Cursor position of Rings (Left(-1) or Right(1))
[32c0841]39        self.xmaxd = self.base.data2D.xmax
40        self.xmind = self.base.data2D.xmin
[ef0c170]41
[32c0841]42        if (self.xmaxd + self.xmind) > 0:
43            self.sign = 1
[a2c38de]44        else:
[32c0841]45            self.sign = -1
[ef0c170]46        # Inner circle
[32c0841]47        self.inner_circle = RingInteractor(self, self.base.subplot,
48                                            zorder=zorder,
49                                            r=self.qmax/2.0, sign=self.sign)
[bd1d9d9]50        self.inner_circle.qmax = self.qmax
[32c0841]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
[ef0c170]55        self.update()
[7ab9241]56        self._post_data()
[ef0c170]57       
58        # Bind to slice parameter events
[1ce365f8]59        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
[a2c38de]60       
[ef0c170]61    def _onEVT_SLICER_PARS(self, event):
[eba08f1a]62        """
[83f4445]63        receive an event containing parameters values to reset the slicer
64       
65        :param event: event of type SlicerParameterEvent with params as
[eba08f1a]66            attribute
[83f4445]67           
[eba08f1a]68        """
[32c0841]69        wx.PostEvent(self.base,
70                     StatusEvent(status="AnnulusSlicer._onEVT_SLICER_PARS"))
[ef0c170]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):
[eba08f1a]77        """
[83f4445]78        Allow adding plot to the same panel
79       
80        :param n: the number of layer
81       
[eba08f1a]82        """
[ef0c170]83        self.layernum = n
84        self.update()
85       
86    def clear(self):
[eba08f1a]87        """
[83f4445]88        Clear the slicer and all connected events related to this slicer
[eba08f1a]89        """
[ef0c170]90        self.clear_markers()
91        self.outer_circle.clear()
92        self.inner_circle.clear()
[18eba35]93        self.base.connect.clearall()
[d468daa]94        self.base.Unbind(EVT_SLICER_PARS)
[ef0c170]95       
96    def update(self):
97        """
[83f4445]98        Respond to changes in the model by recalculating the profiles and
99        resetting the widgets.
[ef0c170]100        """
101        # Update locations       
102        self.inner_circle.update()
103        self.outer_circle.update()
104       
105    def save(self, ev):
106        """
[83f4445]107        Remember the roughness for this layer and the next so that we
108        can restore on Esc.
[ef0c170]109        """
110        self.base.freeze_axes()
111        self.inner_circle.save(ev)
112        self.outer_circle.save(ev)
113
[32c0841]114    def _post_data(self, nbins=None):
[eba08f1a]115        """
[83f4445]116        Uses annulus parameters to plot averaged data into 1D data.
117       
118        :param nbins: the number of points to plot
119       
[eba08f1a]120        """
121        #Data to average
[ef0c170]122        data = self.base.data2D
123        # If we have no data, just return
124        if data == None:
125            return
126       
[78cae5a]127        from DataLoader.manipulations import Ring
[32c0841]128        rmin = min(math.fabs(self.inner_circle.get_radius()),
[3b909b7]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()))
[eba08f1a]132        #if the user does not specify the numbers of points to plot
133        # the default number will be nbins= 20
[32c0841]134        if nbins == None:
135            self.nbins = 20
[eba08f1a]136        else:
137            self.nbins = nbins
138        ## create the data1D Q average of data2D   
[32c0841]139        sect = Ring(r_min=rmin, r_max=rmax, nbins=self.nbins)
[ef0c170]140        sector = sect(self.base.data2D)
[32c0841]141   
142        if hasattr(sector, "dxl"):
143            dxl = sector.dxl
[ef0c170]144        else:
[32c0841]145            dxl = None
146        if hasattr(sector, "dxw"):
147            dxw = sector.dxw
[ef0c170]148        else:
[32c0841]149            dxw = None
150        new_plot = Data1D(x=(sector.x - math.pi) * 180/math.pi,
151                          y=sector.y, dy=sector.dy)
[4ac8556]152        new_plot.dxl = dxl
153        new_plot.dxw = dxw
[78cae5a]154        new_plot.name = "AnnulusPhi" +"("+ self.base.data2D.name+")"
[ef0c170]155       
[32c0841]156        new_plot.source = self.base.data2D.source
[ac9a5f6]157        #new_plot.info=self.base.data2D.info
[ef0c170]158        new_plot.interactive = True
[32c0841]159        new_plot.detector = self.base.data2D.detector
[ef0c170]160        # If the data file does not tell us what the axes are, just assume...
[14d3495]161        new_plot.xaxis("\\rm{\phi}", 'degrees')
[fcf072d]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} ", "/")
[21969a4a]167
[32c0841]168        new_plot.group_id = "AnnulusPhi" + self.base.data2D.name
[21969a4a]169        new_plot.id = None
[70cf5d3]170        #new_plot.is_data= True
[32c0841]171        new_plot.xtransform = "x"
172        new_plot.ytransform = "y"
[ef0c170]173        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
[32c0841]174                                                 title="AnnulusPhi"))
[ef0c170]175       
176    def moveend(self, ev):
[eba08f1a]177        """
[83f4445]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
[eba08f1a]182        """
[ef0c170]183        self.base.thaw_axes()
[eba08f1a]184        # Post parameters to plotter 2D
[0d9dae8]185        event = SlicerParameterEvent()
[ef0c170]186        event.type = self.__class__.__name__
187        event.params = self.get_params()
[d468daa]188        wx.PostEvent(self.base, event)
[eba08f1a]189        # create a 1D data plot
[78cae5a]190        #self._post_data()
[ef0c170]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):
[eba08f1a]209        """
[83f4445]210        Store a copy of values of parameters of the slicer into a dictionary.
211       
212        :return params: the dictionary created
213       
[eba08f1a]214        """
[ef0c170]215        params = {}
[0bd125d]216        params["inner_radius"] = math.fabs(self.inner_circle._inner_mouse_x)
217        params["outer_radius"] = math.fabs(self.outer_circle._inner_mouse_x)
[ef0c170]218        params["nbins"] = self.nbins
219        return params
220   
221    def set_params(self, params):
[eba08f1a]222        """
[83f4445]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
[eba08f1a]227            values the user assigned to the slicer.
[83f4445]228           
[eba08f1a]229        """
[32c0841]230        inner = math.fabs(params["inner_radius"])
231        outer = math.fabs(params["outer_radius"])
[ef0c170]232        self.nbins = int(params["nbins"])
[eba08f1a]233        ## Update the picture
[ef0c170]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)
[eba08f1a]236        ## Post the data given the nbins entered by the user
[5554566]237        self._post_data(self.nbins)
[ef0c170]238       
239    def freeze_axes(self):
[83f4445]240        """
241        """
[ef0c170]242        self.base.freeze_axes()
243       
244    def thaw_axes(self):
[83f4445]245        """
246        """
[ef0c170]247        self.base.thaw_axes()
248
249    def draw(self):
[83f4445]250        """
251        """
[ef0c170]252        self.base.draw()
253
254       
255class RingInteractor(_BaseInteractor):
256    """
[83f4445]257     Draw a ring Given a radius
[ef0c170]258    """
[32c0841]259    def __init__(self, base, axes, color='black', zorder=5, r=1.0, sign=1):
[83f4445]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
[ef0c170]264       
[83f4445]265        """
[ef0c170]266        _BaseInteractor.__init__(self, base, axes, color=color)
267        self.markers = []
268        self.axes = axes
[eba08f1a]269        # Current radius of the ring
[ef0c170]270        self._inner_mouse_x = r
[eba08f1a]271        #Value of the center of the ring
[ef0c170]272        self._inner_mouse_y = 0
[eba08f1a]273        # previous value of that radius
[ef0c170]274        self._inner_save_x  = r
[eba08f1a]275        #Save value of the center of the ring
[ef0c170]276        self._inner_save_y  = 0
[eba08f1a]277        #Class instantiating RingIterator class
[32c0841]278        self.base = base
[eba08f1a]279        #the direction of the motion of the marker
[32c0841]280        self.sign = sign
[eba08f1a]281        ## Create a marker
[ef0c170]282        try:
283            # Inner circle marker
[32c0841]284            x_value = [self.sign * math.fabs(self._inner_mouse_x)]
285            self.inner_marker = self.axes.plot(x_value,
286                                               [0],
287                                                linestyle='',
[ef0c170]288                                          marker='s', markersize=10,
289                                          color=self.color, alpha=0.6,
290                                          pickradius=5, label="pick", 
[32c0841]291                                          zorder=zorder,
[ef0c170]292                                          visible=True)[0]
293        except:
[32c0841]294            x_value = [self.sign * math.fabs(self._inner_mouse_x)]
295            self.inner_marker = self.axes.plot(x_value,
296                                               [0], 
297                                               linestyle='',
[ef0c170]298                                          marker='s', markersize=10,
299                                          color=self.color, alpha=0.6,
300                                          label="pick", 
301                                          visible=True)[0]
[32c0841]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"
[ef0c170]306           
[32c0841]307            owner = self.base.base.parent
308            wx.PostEvent(owner, 
309                         StatusEvent(status="AnnulusSlicer: %s" % message))
[ef0c170]310           
[eba08f1a]311        # Draw a circle
[32c0841]312        [self.inner_circle] = self.axes.plot([], [],
[ef0c170]313                                      linestyle='-', marker='',
314                                      color=self.color)
[eba08f1a]315        # the number of points that make the ring line
[e4032d64]316        self.npts = 40
[ef0c170]317           
318        self.connect_markers([self.inner_marker])
319        self.update()
320
321    def set_layer(self, n):
[eba08f1a]322        """
[83f4445]323        Allow adding plot to the same panel
324         
325        :param n: the number of layer
326       
[eba08f1a]327        """
[ef0c170]328        self.layernum = n
329        self.update()
330       
331    def clear(self):
[eba08f1a]332        """
[83f4445]333        Clear the slicer and all connected events related to this slicer
[eba08f1a]334        """
[ef0c170]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):
[eba08f1a]345        """
[83f4445]346        :return self._inner_mouse_x: the current radius of the ring
[eba08f1a]347        """
[ef0c170]348        return self._inner_mouse_x
349       
350    def update(self):
351        """
[83f4445]352        Draw the new roughness on the graph.
[ef0c170]353        """
354        # Plot inner circle
355        x = []
356        y = []
357        for i in range(self.npts):
[32c0841]358            phi = 2.0 * math.pi / (self.npts - 1) * i
[ef0c170]359           
[32c0841]360            xval = 1.0 * self._inner_mouse_x * math.cos(phi) 
361            yval = 1.0 * self._inner_mouse_x * math.sin(phi) 
[ef0c170]362           
363            x.append(xval)
364            y.append(yval)
[a2c38de]365           
[32c0841]366        self.inner_marker.set(xdata=[self.sign*math.fabs(self._inner_mouse_x)],
367                              ydata=[0])
[ef0c170]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):
[eba08f1a]380        """
[83f4445]381        Called after a dragging motion
[eba08f1a]382        """
[ef0c170]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):
[eba08f1a]401        """
[83f4445]402        draw the ring given x, y value
[eba08f1a]403        """
[ef0c170]404        self.move(x, y, None)
405        self.update()
406       
407       
408    def get_params(self):
[eba08f1a]409        """
[83f4445]410        Store a copy of values of parameters of the slicer into a dictionary.
411       
412        :return params: the dictionary created
413       
[eba08f1a]414        """
[ef0c170]415        params = {}
[a2c38de]416        params["radius"] = math.fabs(self._inner_mouse_x)
[ef0c170]417        return params
418   
419    def set_params(self, params):
[eba08f1a]420        """
[83f4445]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
[eba08f1a]425            values the user assigned to the slicer.
[83f4445]426           
[eba08f1a]427        """
[ef0c170]428        x = params["radius"] 
429        self.set_cursor(x, self._inner_mouse_y)
430       
[c5874f2]431class CircularMask(_BaseInteractor):
432    """
[83f4445]433     Draw a ring Given a radius
[c5874f2]434    """
[32c0841]435    def __init__(self, base, axes, color='grey', zorder=3, side=None):
[83f4445]436        """
[c5874f2]437       
[83f4445]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        """
[c5874f2]443        _BaseInteractor.__init__(self, base, axes, color=color)
444        self.markers = []
445        self.axes = axes
[32c0841]446        self.base = base
[c5874f2]447        self.is_inside = side
[32c0841]448        self.qmax = min(math.fabs(self.base.data.xmax),
449                        math.fabs(self.base.data.xmin))  #must be positive
[c5874f2]450        self.connect = self.base.connect
451       
452        #Cursor position of Rings (Left(-1) or Right(1))
[32c0841]453        self.xmaxd = self.base.data.xmax
454        self.xmind = self.base.data.xmin
[c5874f2]455
[32c0841]456        if (self.xmaxd + self.xmind) > 0:
457            self.sign = 1
[c5874f2]458        else:
[32c0841]459            self.sign = -1
[c5874f2]460        # Inner circle
[32c0841]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
[c5874f2]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        """
[83f4445]473        receive an event containing parameters values to reset the slicer
474       
475        :param event: event of type SlicerParameterEvent with params as
[c5874f2]476            attribute
477        """
[32c0841]478        wx.PostEvent(self.base,
479                     StatusEvent(status="AnnulusSlicer._onEVT_SLICER_PARS"))
[c5874f2]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        """
[83f4445]487        Allow adding plot to the same panel
488         
489        :param n: the number of layer
490       
[c5874f2]491        """
492        self.layernum = n
493        self.update()
494       
495    def clear(self):
496        """
[83f4445]497        Clear the slicer and all connected events related to this slicer
[c5874f2]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        """
[83f4445]506        Respond to changes in the model by recalculating the profiles and
507        resetting the widgets.
[c5874f2]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        """
[83f4445]517        Remember the roughness for this layer and the next so that we
518        can restore on Esc.
[c5874f2]519        """
520        self.base.freeze_axes()
521        self.outer_circle.save(ev)
522
523    def _post_data(self):
524        """
[83f4445]525        Uses annulus parameters to plot averaged data into 1D data.
526       
527        :param nbins: the number of points to plot
528       
[c5874f2]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 DataLoader.manipulations import Ringcut
538   
[32c0841]539        rmin = 0
[c5874f2]540        rmax = math.fabs(self.outer_circle.get_radius())
541
542        ## create the data1D Q average of data2D   
[32c0841]543        mask = Ringcut(r_min=rmin, r_max= rmax)
[c5874f2]544
545        if self.is_inside:
[32c0841]546            out = (mask(data) == False)
[c5874f2]547        else:
548            out = (mask(data))
549        #self.base.data.mask=out
550        return out                   
551
552         
553    def moveend(self, ev):
554        """
[83f4445]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
[c5874f2]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        """
[83f4445]581        Store a copy of values of parameters of the slicer into a dictionary.
582       
583        :return params: the dictionary created
584       
[c5874f2]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        """
[83f4445]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
[c5874f2]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.