source: sasview/src/sas/guiframe/local_perspectives/plotting/boxSlicer.py @ 4f1791e

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 4f1791e was 79492222, checked in by krzywon, 10 years ago

Changed the file and folder names to remove all SANS references.

  • Property mode set to 100644
File size: 20.1 KB
RevLine 
[2d107b8]1
2
[4ac8556]3import wx
[32c0841]4#import copy
5#from copy import deepcopy
[4ac8556]6import math
7import numpy
[79492222]8from sas.guiframe.events import NewPlotEvent
9from sas.guiframe.events import StatusEvent
10from sas.guiframe.events import SlicerParameterEvent
11from sas.guiframe.events import EVT_SLICER_PARS
[4ac8556]12from BaseInteractor import _BaseInteractor
[79492222]13from sas.guiframe.dataFitting import Data1D
[32c0841]14#import SlicerParameters
[2d107b8]15
[0f6d05f8]16
[38224f10]17class BoxInteractor(_BaseInteractor):
[2d107b8]18    """
[83f4445]19    BoxInteractor define a rectangle that return data1D average of Data2D
20    in a rectangle area defined by -x, x ,y, -y
[2d107b8]21    """
[32c0841]22    def __init__(self, base, axes, color='black', zorder=3):
[2d107b8]23        _BaseInteractor.__init__(self, base, axes, color=color)
[6c0568b]24        ## Class initialization
[2d107b8]25        self.markers = []
26        self.axes = axes
[6c0568b]27        ##connecting artist
[2d107b8]28        self.connect = self.base.connect
[8f56250]29        ## which direction is the preferred interaction direction
30        self.direction = None
[6c0568b]31        ## determine x y  values
[32c0841]32        self.x = 0.5 * min(math.fabs(self.base.data2D.xmax),
33                           math.fabs(self.base.data2D.xmin))
34        self.y = 0.5 * min(math.fabs(self.base.data2D.xmax),
35                           math.fabs(self.base.data2D.xmin))       
[6c0568b]36        ## when reach qmax reset the graph
[32c0841]37        self.qmax = max(self.base.data2D.xmax, self.base.data2D.xmin,
38                        self.base.data2D.ymax, self.base.data2D.ymin)   
[2d107b8]39        ## Number of points on the plot
[ffd23b5]40        self.nbins = 30
[32c0841]41        ## If True, I(|Q|) will be return, otherwise,
42        #negative q-values are allowed
[a4d4b35]43        self.fold = True       
[6c0568b]44        ## reference of the current  Slab averaging
[32c0841]45        self.averager = None
[6c0568b]46        ## Create vertical and horizaontal lines for the rectangle
[32c0841]47        self.vertical_lines = VerticalLines(self,
48                                            self.base.subplot, 
49                                            color='blue', 
50                                            zorder=zorder,
51                                            y=self.y ,
52                                            x=self.x)
[18eba35]53        self.vertical_lines.qmax = self.qmax
54       
[32c0841]55        self.horizontal_lines = HorizontalLines(self,
56                                               self.base.subplot,
57                                               color='green', 
58                                               zorder=zorder,
59                                               x=self.x,
60                                               y=self.y)
61        self.horizontal_lines.qmax = self.qmax
[6c0568b]62        ## draw the rectangle and plost the data 1D resulting
63        ## of averaging data2D
[38224f10]64        self.update()
[0f6d05f8]65        self._post_data()
[6c0568b]66        ## Bind to slice parameter events
[18eba35]67        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
[2d107b8]68
69    def _onEVT_SLICER_PARS(self, event):
[6c0568b]70        """
[83f4445]71        receive an event containing parameters values to reset the slicer
72       
73        :param event: event of type SlicerParameterEvent with params as
[6c0568b]74            attribute
75        """
[32c0841]76        wx.PostEvent(self.base.parent,
77                     StatusEvent(status="BoxSlicer._onEVT_SLICER_PARS"))
[2d107b8]78        event.Skip()
79        if event.type == self.__class__.__name__:
[0f6d05f8]80            self.set_params(event.params)
[2d107b8]81            self.base.update()
[32c0841]82           
[2d107b8]83    def update_and_post(self):
[6c0568b]84        """
[83f4445]85        Update the slicer and plot the resulting data
[6c0568b]86        """
[2d107b8]87        self.update()
88        self._post_data()
[83f4445]89       
[2d107b8]90    def set_layer(self, n):
[6c0568b]91        """
[83f4445]92        Allow adding plot to the same panel
93       
94        :param n: the number of layer
95       
[6c0568b]96        """
[2d107b8]97        self.layernum = n
98        self.update()
99       
100    def clear(self):
[6c0568b]101        """
[83f4445]102        Clear the slicer and all connected events related to this slicer
[6c0568b]103        """
[32c0841]104        self.averager = None
[2d107b8]105        self.clear_markers()
[18eba35]106        self.horizontal_lines.clear()
107        self.vertical_lines.clear()
108        self.base.connect.clearall()
109        self.base.Unbind(EVT_SLICER_PARS)
[2d107b8]110       
111    def update(self):
112        """
113        Respond to changes in the model by recalculating the profiles and
114        resetting the widgets.
115        """
[6c0568b]116        ##Update the slicer if an horizontal line is dragged   
[18eba35]117        if self.horizontal_lines.has_move:
118            self.horizontal_lines.update()
119            self.vertical_lines.update(y=self.horizontal_lines.y)
[6c0568b]120        ##Update the slicer if a vertical line is dragged   
[18eba35]121        if self.vertical_lines.has_move:
122            self.vertical_lines.update()
123            self.horizontal_lines.update(x=self.vertical_lines.x)
[83f4445]124                 
[2d107b8]125    def save(self, ev):
126        """
127        Remember the roughness for this layer and the next so that we
128        can restore on Esc.
129        """
130        self.base.freeze_axes()
[18eba35]131        self.vertical_lines.save(ev)
132        self.horizontal_lines.save(ev)
133   
[ffd23b5]134    def _post_data(self):
135        pass
136       
[32c0841]137    def post_data(self, new_slab=None, nbins=None, direction=None):
[6c0568b]138        """
[83f4445]139        post data averaging in Qx or Qy given new_slab type
140       
141        :param new_slab: slicer that determine with direction to average
142        :param nbins: the number of points plotted when averaging
143        :param direction: the direction of averaging
144       
[6c0568b]145        """
[8f56250]146        if self.direction == None:
147            self.direction = direction
148           
[32c0841]149        x_min = -1 * math.fabs(self.vertical_lines.x)
150        x_max = math.fabs(self.vertical_lines.x)
151        y_min = -1 * math.fabs(self.horizontal_lines.y)
152        y_max = math.fabs(self.horizontal_lines.y)
[ffd23b5]153       
[32c0841]154        if nbins != None:
155            self.nbins = nbins
156        if self.averager == None:
157            if new_slab == None:
158                msg = "post data:cannot average , averager is empty"
159                raise ValueError, msg
160            self.averager = new_slab
[8f56250]161        if self.direction == "X":
[32c0841]162            if self.fold:
163                x_low = 0
164            else:
165                x_low = math.fabs(x_min)
166            bin_width = (x_max + x_low)/self.nbins
[8f56250]167        elif self.direction == "Y":
[32c0841]168            if self.fold:
169                y_low = 0
170            else:
171                y_low = math.fabs(y_min)
172            bin_width = (y_max + y_low)/self.nbins
[8f56250]173        else:
174            msg = "post data:no Box Average direction was supplied"
175            raise ValueError, msg
[6c0568b]176        ## Average data2D given Qx or Qy
[32c0841]177        box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max,
[ffd23b5]178                         bin_width=bin_width)
[a4d4b35]179        box.fold = self.fold
[ffd23b5]180        boxavg = box(self.base.data2D)
[6c0568b]181        #3 Create Data1D to plot
[32c0841]182        if hasattr(boxavg, "dxl"):
183            dxl = boxavg.dxl
[ffd23b5]184        else:
[32c0841]185            dxl = None
186        if hasattr(boxavg, "dxw"):
187            dxw = boxavg.dxw
[ffd23b5]188        else:
[32c0841]189            dxw = None
190        new_plot = Data1D(x=boxavg.x, y=boxavg.y, dy=boxavg.dy)
[4ac8556]191        new_plot.dxl  = dxl
192        new_plot.dxw  = dxw
[32c0841]193        new_plot.name = str(self.averager.__name__) + \
194                        "(" + self.base.data2D.name + ")"
195        new_plot.source = self.base.data2D.source
[ffd23b5]196        new_plot.interactive = True
[32c0841]197        new_plot.detector = self.base.data2D.detector
[21969a4a]198        ## If the data file does not tell us what the axes are, just assume...
199        new_plot.xaxis("\\rm{Q}", "A^{-1}")
[fcf072d]200        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
201
202        data = self.base.data2D
203        if hasattr(data, "scale") and data.scale == 'linear' and \
204                self.base.data2D.name.count("Residuals") > 0:
205            new_plot.ytransform = 'y'
206            new_plot.yaxis("\\rm{Residuals} ", "/")
207           
[940aca7]208        new_plot.group_id = "2daverage" + self.base.data2D.name
209        new_plot.id = (self.averager.__name__) + self.base.data2D.name
210        new_plot.is_data= True
[13382fc7]211        self.base.parent.update_theory(data_id=self.base.data2D.id, \
212                                       theory=new_plot)
[ffd23b5]213        wx.PostEvent(self.base.parent, NewPlotEvent(plot=new_plot,
[32c0841]214                                title=str(self.averager.__name__)))
[83f4445]215         
[2d107b8]216    def moveend(self, ev):
[6c0568b]217        """
[83f4445]218        Called after a dragging event.
219        Post the slicer new parameters and creates a new Data1D
220        corresponding to the new average
[6c0568b]221        """
[2d107b8]222        self.base.thaw_axes()
223        # Post paramters
[0d9dae8]224        event = SlicerParameterEvent()
[2d107b8]225        event.type = self.__class__.__name__
[0f6d05f8]226        event.params = self.get_params()
[2d107b8]227        wx.PostEvent(self.base.parent, event)
[6c0568b]228        # create the new data1D
[2d107b8]229        self._post_data()
230           
231    def restore(self):
232        """
233        Restore the roughness for this layer.
234        """
[18eba35]235        self.horizontal_lines.restore()
236        self.vertical_lines.restore()
237       
[2d107b8]238    def move(self, x, y, ev):
239        """
240        Process move to a new position, making sure that the move is allowed.
241        """
242        pass
243       
244    def set_cursor(self, x, y):
245        pass
246       
247    def get_params(self):
[6c0568b]248        """
[83f4445]249        Store a copy of values of parameters of the slicer into a dictionary.
250       
251        :return params: the dictionary created
252       
[6c0568b]253        """
[2d107b8]254        params = {}
[32c0841]255        params["x_max"] = math.fabs(self.vertical_lines.x)
256        params["y_max"] = math.fabs(self.horizontal_lines.y)
257        params["nbins"] = self.nbins
[2d107b8]258        return params
259   
260    def set_params(self, params):
[6c0568b]261        """
[83f4445]262        Receive a dictionary and reset the slicer with values contained
263        in the values of the dictionary.
264       
265        :param params: a dictionary containing name of slicer parameters and
[6c0568b]266            values the user assigned to the slicer.
267        """
[0f6d05f8]268        self.x = float(math.fabs(params["x_max"]))
269        self.y = float(math.fabs(params["y_max"] ))
[32c0841]270        self.nbins = params["nbins"]
[18eba35]271       
[32c0841]272        self.horizontal_lines.update(x=self.x, y=self.y)
273        self.vertical_lines.update(x=self.x, y=self.y)
274        self.post_data(nbins=None)
[18eba35]275       
[2d107b8]276    def freeze_axes(self):
[83f4445]277        """
278        """
[2d107b8]279        self.base.freeze_axes()
280       
281    def thaw_axes(self):
[83f4445]282        """
283        """
[2d107b8]284        self.base.thaw_axes()
285
286    def draw(self):
[83f4445]287        """
288        """
[2d107b8]289        self.base.draw()
290
[6c0568b]291
[18eba35]292class HorizontalLines(_BaseInteractor):
[2d107b8]293    """
[d955bf19]294    Draw 2 Horizontal lines centered on (0,0) that can move
295    on the x- direction and in opposite direction
[2d107b8]296    """
[32c0841]297    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
298        """
299        """
[2d107b8]300        _BaseInteractor.__init__(self, base, axes, color=color)
[6c0568b]301        ##Class initialization
[2d107b8]302        self.markers = []
303        self.axes = axes
[6c0568b]304        ## Saving the end points of two lines
[32c0841]305        self.x = x
306        self.save_x = x
[3554b99a]307       
[32c0841]308        self.y = y
309        self.save_y = y
[6c0568b]310        ## Creating a marker
[18eba35]311        try:
312            # Inner circle marker
[32c0841]313            self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
[18eba35]314                                          marker='s', markersize=10,
315                                          color=self.color, alpha=0.6,
316                                          pickradius=5, label="pick", 
[32c0841]317                                          # Prefer this to other lines
318                                          zorder=zorder, 
[18eba35]319                                          visible=True)[0]
320        except:
[32c0841]321            self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
[18eba35]322                                          marker='s', markersize=10,
323                                          color=self.color, alpha=0.6,
[32c0841]324                                          label="pick", visible=True)[0]
325            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION "
326            message += "OF MATPLOTLIB\n Get the SVN version that"
327            message += " is at least as recent as June 1, 2007"
328            owner = self.base.base.parent
329            wx.PostEvent(owner,
330                         StatusEvent(status="AnnulusSlicer: %s" % message))
[6c0568b]331        ## Define 2 horizontal lines
[32c0841]332        self.top_line = self.axes.plot([self.x, -self.x], [self.y, self.y],
[18eba35]333                                      linestyle='-', marker='',
[32c0841]334                                      color=self.color, visible=True)[0]
335        self.bottom_line = self.axes.plot([self.x, -self.x], [-self.y, -self.y],
[2d107b8]336                                      linestyle='-', marker='',
[32c0841]337                                      color=self.color, visible=True)[0]
[6c0568b]338        ## Flag to check the motion of the lines
[32c0841]339        self.has_move = False
[6c0568b]340        ## Connecting markers to mouse events and draw
[18eba35]341        self.connect_markers([self.top_line, self.inner_marker])
[3554b99a]342        self.update()
[2d107b8]343
344    def set_layer(self, n):
[6c0568b]345        """
[83f4445]346        Allow adding plot to the same panel
347       
348        :param n: the number of layer
349       
[6c0568b]350        """
[2d107b8]351        self.layernum = n
352        self.update()
353       
354    def clear(self):
[6c0568b]355        """
[83f4445]356        Clear this slicer  and its markers
[6c0568b]357        """
[2d107b8]358        self.clear_markers()
359        try:
[18eba35]360            self.inner_marker.remove()
361            self.top_line.remove() 
362            self.bottom_line.remove()
[2d107b8]363        except:
364            # Old version of matplotlib
365            for item in range(len(self.axes.lines)):
366                del self.axes.lines[0]
[030873e]367   
[32c0841]368    def update(self, x=None, y=None):
[2d107b8]369        """
[83f4445]370        Draw the new roughness on the graph.
371       
372        :param x: x-coordinates to reset current class x
373        :param y: y-coordinates to reset current class y
374       
[2d107b8]375        """
[6c0568b]376        ## Reset x, y- coordinates if send as parameters
[32c0841]377        if x != None:
378            self.x = numpy.sign(self.x) * math.fabs(x)
379        if y != None:
380            self.y = numpy.sign(self.y) * math.fabs(y)
[6c0568b]381        ## Draw lines and markers
[32c0841]382        self.inner_marker.set(xdata=[0], ydata=[self.y])
383        self.top_line.set(xdata=[self.x, -self.x], ydata=[self.y, self.y])
384        self.bottom_line.set(xdata=[self.x,-self.x], ydata=[-self.y, -self.y])
[2d107b8]385       
386    def save(self, ev):
387        """
388        Remember the roughness for this layer and the next so that we
389        can restore on Esc.
390        """
[32c0841]391        self.save_x = self.x
392        self.save_y = self.y
[2d107b8]393        self.base.freeze_axes()
394
395    def moveend(self, ev):
[6c0568b]396        """
[83f4445]397        Called after a dragging this edge and set self.has_move to False
398        to specify the end of dragging motion
[6c0568b]399        """
[32c0841]400        self.has_move = False
[2d107b8]401        self.base.moveend(ev)
[83f4445]402             
[2d107b8]403    def restore(self):
404        """
405        Restore the roughness for this layer.
406        """
[18eba35]407        self.x = self.save_x
408        self.y = self.save_y
[0f6d05f8]409       
[2d107b8]410    def move(self, x, y, ev):
411        """
412        Process move to a new position, making sure that the move is allowed.
413        """
[32c0841]414        self.y = y
415        self.has_move = True
[2d107b8]416        self.base.base.update()
417       
[18eba35]418 
419class VerticalLines(_BaseInteractor):
[2d107b8]420    """
[83f4445]421    Select an annulus through a 2D plot
[2d107b8]422    """
[32c0841]423    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
424        """
425        """
[2d107b8]426        _BaseInteractor.__init__(self, base, axes, color=color)
427        self.markers = []
428        self.axes = axes
[32c0841]429        self.x = math.fabs(x)
430        self.save_x = self.x
431        self.y = math.fabs(y)
432        self.save_y = y
[18eba35]433        try:
434            # Inner circle marker
[32c0841]435            self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
[18eba35]436                                          marker='s', markersize=10,
437                                          color=self.color, alpha=0.6,
438                                          pickradius=5, label="pick", 
[32c0841]439                                          # Prefer this to other lines
440                                          zorder=zorder, visible=True)[0]
[18eba35]441        except:
[32c0841]442            self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
[18eba35]443                                          marker='s', markersize=10,
444                                          color=self.color, alpha=0.6,
[32c0841]445                                          label="pick", visible=True)[0]
446            message  = "\nTHIS PROTOTYPE NEEDS THE LATEST VERSION"
447            message += " OF MATPLOTLIB\n Get the SVN version that is"
448            message += " at least as recent as June 1, 2007"
[18eba35]449           
[32c0841]450        self.right_line = self.axes.plot([self.x, self.x],
451                                         [self.y , -self.y],
[18eba35]452                                      linestyle='-', marker='',
[32c0841]453                                      color=self.color, visible=True)[0]
454        self.left_line = self.axes.plot([-self.x, -self.x],
455                                        [self.y, -self.y],
[2d107b8]456                                      linestyle='-', marker='',
[32c0841]457                                      color=self.color, visible=True)[0]
458        self.has_move = False
[18eba35]459        self.connect_markers([self.right_line, self.inner_marker])
[2d107b8]460        self.update()
461
462    def set_layer(self, n):
[6c0568b]463        """
[83f4445]464        Allow adding plot to the same panel
465       
466        :param n: the number of layer
467       
[6c0568b]468        """
[2d107b8]469        self.layernum = n
470        self.update()
471       
472    def clear(self):
[6c0568b]473        """
[83f4445]474        Clear this slicer  and its markers
[6c0568b]475        """
[2d107b8]476        self.clear_markers()
477        try:
[18eba35]478            self.inner_marker.remove()
479            self.left_line.remove()
480            self.right_line.remove()
[2d107b8]481        except:
482            # Old version of matplotlib
483            for item in range(len(self.axes.lines)):
484                del self.axes.lines[0]
[18eba35]485
[32c0841]486    def update(self, x=None, y=None):
[2d107b8]487        """
[83f4445]488        Draw the new roughness on the graph.
489       
490        :param x: x-coordinates to reset current class x
491        :param y: y-coordinates to reset current class y
492       
[2d107b8]493        """
[6c0568b]494        ## reset x, y -coordinates if given as parameters
[32c0841]495        if x != None:
496            self.x = numpy.sign(self.x) * math.fabs(x)
497        if y != None:
498            self.y = numpy.sign(self.y) * math.fabs(y)
[6c0568b]499        ## draw lines and markers 
[32c0841]500        self.inner_marker.set(xdata=[self.x], ydata=[0]) 
501        self.left_line.set(xdata=[-self.x, -self.x], ydata=[self.y, -self.y]) 
502        self.right_line.set(xdata=[self.x, self.x], ydata=[self.y, -self.y]) 
[0f6d05f8]503   
[2d107b8]504    def save(self, ev):
505        """
506        Remember the roughness for this layer and the next so that we
507        can restore on Esc.
508        """
[83f4445]509        self.save_x = self.x
510        self.save_y = self.y
[2d107b8]511        self.base.freeze_axes()
[83f4445]512       
[2d107b8]513    def moveend(self, ev):
[6c0568b]514        """
[83f4445]515        Called after a dragging this edge and set self.has_move to False
516        to specify the end of dragging motion
[6c0568b]517        """
[32c0841]518        self.has_move = False
[2d107b8]519        self.base.moveend(ev)
[83f4445]520               
[2d107b8]521    def restore(self):
522        """
523        Restore the roughness for this layer.
524        """
[18eba35]525        self.x = self.save_x
526        self.y = self.save_y
[3554b99a]527     
[2d107b8]528    def move(self, x, y, ev):
529        """
530        Process move to a new position, making sure that the move is allowed.
531        """
[32c0841]532        self.has_move = True
533        self.x = x
[8ff3ec1]534        self.base.base.update()
535       
[18eba35]536   
[ffd23b5]537class BoxInteractorX(BoxInteractor):
[eba08f1a]538    """
[83f4445]539    Average in Qx direction
[eba08f1a]540    """
[32c0841]541    def __init__(self, base, axes, color='black', zorder=3):
[ffd23b5]542        BoxInteractor.__init__(self, base, axes, color=color)
[32c0841]543        self.base = base
[ffd23b5]544        self._post_data()
[6c0568b]545       
[ffd23b5]546    def _post_data(self):
[6c0568b]547        """
[83f4445]548        Post data creating by averaging in Qx direction
[6c0568b]549        """
[79492222]550        from sas.dataloader.manipulations import SlabX
[32c0841]551        self.post_data(SlabX, direction="X")   
[ffd23b5]552       
553
554class BoxInteractorY(BoxInteractor):
[eba08f1a]555    """
[83f4445]556    Average in Qy direction
[eba08f1a]557    """
[32c0841]558    def __init__(self, base, axes, color='black', zorder=3):
[ffd23b5]559        BoxInteractor.__init__(self, base, axes, color=color)
[32c0841]560        self.base = base
[ffd23b5]561        self._post_data()
[6c0568b]562       
[ffd23b5]563    def _post_data(self):
[6c0568b]564        """
[83f4445]565        Post data creating by averaging in Qy direction
[6c0568b]566        """
[79492222]567        from sas.dataloader.manipulations import SlabY
[32c0841]568        self.post_data(SlabY, direction="Y")   
[ffd23b5]569       
[2d107b8]570       
Note: See TracBrowser for help on using the repository browser.