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

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 b699768 was b699768, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Initial commit of the refactored SasCalc? module.

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