source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 2aba0a4

Last change on this file since 2aba0a4 was 7432acb, checked in by andyfaff, 8 years ago

MAINT: search+replace '!= None' by 'is not None'

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