source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/boxSlicer.py @ ba152f1

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