source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/AnnulusSlicer.py @ 83eb5208

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 83eb5208 was 83eb5208, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Putting files in more ordered fashion

  • Property mode set to 100644
File size: 16.8 KB
RevLine 
[3bdbfcc]1import numpy
2from PyQt4 import QtGui
3from PyQt4 import QtCore
4
[ef0c170]5from BaseInteractor import _BaseInteractor
[d85c194]6from sas.sasgui.guiframe.dataFitting import Data1D
[83eb5208]7import sas.qtgui.Utilities.GuiUtils as GuiUtils
8from sas.qtgui.Utilities.GuiUtils import formatNumber
9from sas.qtgui.Plotting.SlicerModel import SlicerModel
[ef0c170]10
[3bdbfcc]11class AnnulusInteractor(_BaseInteractor, SlicerModel):
[ef0c170]12    """
[83f4445]13    Select an annulus through a 2D plot.
[824e488]14    This interactor is used to average 2D data  with the region
[83f4445]15    defined by 2 radius.
16    this class is defined by 2 Ringinterators.
[ef0c170]17    """
[3bdbfcc]18    def __init__(self, base, axes, item=None, color='black', zorder=3):
[824e488]19
[ef0c170]20        _BaseInteractor.__init__(self, base, axes, color=color)
[3bdbfcc]21        SlicerModel.__init__(self)
22
[ef0c170]23        self.markers = []
24        self.axes = axes
[32c0841]25        self.base = base
[3bdbfcc]26        self._item = item
27        self.qmax = min(numpy.fabs(self.base.data.xmax),
28                        numpy.fabs(self.base.data.xmin))  # must be positive
[ef0c170]29        self.connect = self.base.connect
[824e488]30
[3bdbfcc]31        # Number of points on the plot
[400155b]32        self.nbins = 36
[824e488]33        # Cursor position of Rings (Left(-1) or Right(1))
[3bdbfcc]34        self.xmaxd = self.base.data.xmax
35        self.xmind = self.base.data.xmin
[ef0c170]36
[32c0841]37        if (self.xmaxd + self.xmind) > 0:
38            self.sign = 1
[a2c38de]39        else:
[32c0841]40            self.sign = -1
[ef0c170]41        # Inner circle
[3bdbfcc]42        self.inner_circle = RingInteractor(self, self.axes,
[824e488]43                                           zorder=zorder,
44                                           r=self.qmax / 2.0, sign=self.sign)
[bd1d9d9]45        self.inner_circle.qmax = self.qmax
[3bdbfcc]46        self.outer_circle = RingInteractor(self, self.axes,
[824e488]47                                           zorder=zorder + 1, r=self.qmax / 1.8,
[32c0841]48                                           sign=self.sign)
49        self.outer_circle.qmax = self.qmax * 1.2
[ef0c170]50        self.update()
[7ab9241]51        self._post_data()
[824e488]52
[3bdbfcc]53        self.setModelFromParams()
[ef0c170]54
55    def set_layer(self, n):
[eba08f1a]56        """
[83f4445]57        Allow adding plot to the same panel
[824e488]58
[83f4445]59        :param n: the number of layer
[824e488]60
[eba08f1a]61        """
[ef0c170]62        self.layernum = n
63        self.update()
[824e488]64
[ef0c170]65    def clear(self):
[eba08f1a]66        """
[83f4445]67        Clear the slicer and all connected events related to this slicer
[eba08f1a]68        """
[ef0c170]69        self.clear_markers()
70        self.outer_circle.clear()
71        self.inner_circle.clear()
[18eba35]72        self.base.connect.clearall()
[824e488]73
[ef0c170]74    def update(self):
75        """
[83f4445]76        Respond to changes in the model by recalculating the profiles and
77        resetting the widgets.
[ef0c170]78        """
[824e488]79        # Update locations
[ef0c170]80        self.inner_circle.update()
81        self.outer_circle.update()
[824e488]82
[ef0c170]83    def save(self, ev):
84        """
[83f4445]85        Remember the roughness for this layer and the next so that we
86        can restore on Esc.
[ef0c170]87        """
88        self.inner_circle.save(ev)
89        self.outer_circle.save(ev)
90
[32c0841]91    def _post_data(self, nbins=None):
[eba08f1a]92        """
[83f4445]93        Uses annulus parameters to plot averaged data into 1D data.
[824e488]94
95        :param nbins: the number of points to plot
96
[eba08f1a]97        """
[824e488]98        # Data to average
[3bdbfcc]99        data = self.base.data
100        if data is None:
[ef0c170]101            return
[824e488]102
[b699768]103        from sas.sascalc.dataloader.manipulations import Ring
[3bdbfcc]104        rmin = min(numpy.fabs(self.inner_circle.get_radius()),
105                   numpy.fabs(self.outer_circle.get_radius()))
106        rmax = max(numpy.fabs(self.inner_circle.get_radius()),
107                   numpy.fabs(self.outer_circle.get_radius()))
108        # If the user does not specify the numbers of points to plot
[400155b]109        # the default number will be nbins= 36
[3bdbfcc]110        if nbins is None:
[400155b]111            self.nbins = 36
[eba08f1a]112        else:
113            self.nbins = nbins
[3bdbfcc]114        # Create the data1D Q average of data2D
[32c0841]115        sect = Ring(r_min=rmin, r_max=rmax, nbins=self.nbins)
[3bdbfcc]116        sector = sect(self.base.data)
[824e488]117
[32c0841]118        if hasattr(sector, "dxl"):
119            dxl = sector.dxl
[ef0c170]120        else:
[32c0841]121            dxl = None
122        if hasattr(sector, "dxw"):
123            dxw = sector.dxw
[ef0c170]124        else:
[32c0841]125            dxw = None
[3bdbfcc]126        new_plot = Data1D(x=(sector.x - numpy.pi) * 180 / numpy.pi,
[32c0841]127                          y=sector.y, dy=sector.dy)
[4ac8556]128        new_plot.dxl = dxl
129        new_plot.dxw = dxw
[3bdbfcc]130        new_plot.name = "AnnulusPhi" + "(" + self.base.data.name + ")"
131        new_plot.title = "AnnulusPhi" + "(" + self.base.data.name + ")"
[824e488]132
[3bdbfcc]133        new_plot.source = self.base.data.source
[ef0c170]134        new_plot.interactive = True
[3bdbfcc]135        new_plot.detector = self.base.data.detector
[ef0c170]136        # If the data file does not tell us what the axes are, just assume...
[14d3495]137        new_plot.xaxis("\\rm{\phi}", 'degrees')
[fcf072d]138        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
139        if hasattr(data, "scale") and data.scale == 'linear' and \
[3bdbfcc]140                self.base.data.name.count("Residuals") > 0:
[fcf072d]141            new_plot.ytransform = 'y'
142            new_plot.yaxis("\\rm{Residuals} ", "/")
[21969a4a]143
[3bdbfcc]144        new_plot.group_id = "AnnulusPhi" + self.base.data.name
145        new_plot.id = "AnnulusPhi" + self.base.data.name
[824e488]146        new_plot.is_data = True
[32c0841]147        new_plot.xtransform = "x"
148        new_plot.ytransform = "y"
[3bdbfcc]149        variant_plot = QtCore.QVariant(new_plot)
150        GuiUtils.updateModelItemWithPlot(self._item, variant_plot, new_plot.id)
[116260a]151        self.base.manager.communicator.plotUpdateSignal.emit([new_plot])
[3bdbfcc]152
153        if self.update_model:
154            self.setModelFromParams()
155        self.draw()
156
[161713c]157    def validate(self, param_name, param_value):
158        """
159        Test the proposed new value "value" for row "row" of parameters
160        """
161        MIN_DIFFERENCE = 0.01
162        isValid = True
163
164        if param_name == 'inner_radius':
165            # First, check the closeness
166            if numpy.fabs(param_value - self.getParams()['outer_radius']) < MIN_DIFFERENCE:
167                print("Inner and outer radii too close. Please adjust.")
168                isValid = False
169            elif param_value > self.qmax:
170                print("Inner radius exceeds maximum range. Please adjust.")
171                isValid = False
172        elif param_name == 'outer_radius':
173            # First, check the closeness
174            if numpy.fabs(param_value - self.getParams()['inner_radius']) < MIN_DIFFERENCE:
175                print("Inner and outer radii too close. Please adjust.")
176                isValid = False
177            elif param_value > self.qmax:
178                print("Outer radius exceeds maximum range. Please adjust.")
179                isValid = False
180        elif param_name == 'nbins':
181            # Can't be 0
182            if param_value < 1:
183                print("Number of bins cannot be less than or equal to 0. Please adjust.")
184                isValid = False
185
186        return isValid
[824e488]187
[ef0c170]188    def moveend(self, ev):
[eba08f1a]189        """
[83f4445]190        Called when any dragging motion ends.
[3bdbfcc]191        Redraw the plot with new parameters.
[eba08f1a]192        """
[3bdbfcc]193        self._post_data(self.nbins)
[824e488]194
[ef0c170]195    def restore(self):
196        """
197        Restore the roughness for this layer.
198        """
199        self.inner_circle.restore()
200        self.outer_circle.restore()
201
202    def move(self, x, y, ev):
203        """
204        Process move to a new position, making sure that the move is allowed.
205        """
206        pass
[824e488]207
[ef0c170]208    def set_cursor(self, x, y):
209        pass
[824e488]210
[3bdbfcc]211    def getParams(self):
[eba08f1a]212        """
[83f4445]213        Store a copy of values of parameters of the slicer into a dictionary.
214        :return params: the dictionary created
[eba08f1a]215        """
[ef0c170]216        params = {}
[3bdbfcc]217        params["inner_radius"] = numpy.fabs(self.inner_circle._inner_mouse_x)
218        params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x)
[ef0c170]219        params["nbins"] = self.nbins
220        return params
[824e488]221
[3bdbfcc]222    def setParams(self, params):
[eba08f1a]223        """
[824e488]224        Receive a dictionary and reset the slicer with values contained
[83f4445]225        in the values of the dictionary.
[824e488]226
227        :param params: a dictionary containing name of slicer parameters and
[eba08f1a]228            values the user assigned to the slicer.
229        """
[3bdbfcc]230        inner = numpy.fabs(params["inner_radius"])
231        outer = numpy.fabs(params["outer_radius"])
[ef0c170]232        self.nbins = int(params["nbins"])
[3bdbfcc]233        # Update the picture
[ef0c170]234        self.inner_circle.set_cursor(inner, self.inner_circle._inner_mouse_y)
235        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
[3bdbfcc]236        # Post the data given the nbins entered by the user
[5554566]237        self._post_data(self.nbins)
[824e488]238
[ef0c170]239    def draw(self):
[83f4445]240        """
241        """
[ef0c170]242        self.base.draw()
243
[824e488]244
[ef0c170]245class RingInteractor(_BaseInteractor):
246    """
[824e488]247     Draw a ring Given a radius
[ef0c170]248    """
[32c0841]249    def __init__(self, base, axes, color='black', zorder=5, r=1.0, sign=1):
[83f4445]250        """
251        :param: the color of the line that defined the ring
252        :param r: the radius of the ring
[824e488]253        :param sign: the direction of motion the the marker
254
[83f4445]255        """
[ef0c170]256        _BaseInteractor.__init__(self, base, axes, color=color)
257        self.markers = []
258        self.axes = axes
[eba08f1a]259        # Current radius of the ring
[ef0c170]260        self._inner_mouse_x = r
[824e488]261        # Value of the center of the ring
[ef0c170]262        self._inner_mouse_y = 0
[eba08f1a]263        # previous value of that radius
[824e488]264        self._inner_save_x = r
265        # Save value of the center of the ring
266        self._inner_save_y = 0
267        # Class instantiating RingIterator class
[32c0841]268        self.base = base
[824e488]269        # the direction of the motion of the marker
[32c0841]270        self.sign = sign
[824e488]271        # # Create a marker
[3bdbfcc]272        # Inner circle marker
273        x_value = [self.sign * numpy.fabs(self._inner_mouse_x)]
274        self.inner_marker = self.axes.plot(x_value, [0], linestyle='',
275                                           marker='s', markersize=10,
276                                           color=self.color, alpha=0.6,
277                                           pickradius=5, label="pick",
278                                           zorder=zorder,
279                                           visible=True)[0]
[824e488]280        # Draw a circle
281        [self.inner_circle] = self.axes.plot([], [], linestyle='-', marker='', color=self.color)
[3bdbfcc]282        # The number of points that make the ring line
[e4032d64]283        self.npts = 40
[824e488]284
[ef0c170]285        self.connect_markers([self.inner_marker])
286        self.update()
287
288    def set_layer(self, n):
[eba08f1a]289        """
[83f4445]290        Allow adding plot to the same panel
[824e488]291
[83f4445]292        :param n: the number of layer
[824e488]293
[eba08f1a]294        """
[ef0c170]295        self.layernum = n
296        self.update()
[824e488]297
[ef0c170]298    def clear(self):
[eba08f1a]299        """
[83f4445]300        Clear the slicer and all connected events related to this slicer
[eba08f1a]301        """
[ef0c170]302        self.clear_markers()
[3bdbfcc]303        self.inner_marker.remove()
304        self.inner_circle.remove()
[824e488]305
[ef0c170]306    def get_radius(self):
[eba08f1a]307        """
[83f4445]308        :return self._inner_mouse_x: the current radius of the ring
[eba08f1a]309        """
[ef0c170]310        return self._inner_mouse_x
[824e488]311
[ef0c170]312    def update(self):
313        """
[83f4445]314        Draw the new roughness on the graph.
[ef0c170]315        """
316        # Plot inner circle
317        x = []
318        y = []
319        for i in range(self.npts):
[3bdbfcc]320            phi = 2.0 * numpy.pi / (self.npts - 1) * i
[824e488]321
[3bdbfcc]322            xval = 1.0 * self._inner_mouse_x * numpy.cos(phi)
323            yval = 1.0 * self._inner_mouse_x * numpy.sin(phi)
[824e488]324
[ef0c170]325            x.append(xval)
326            y.append(yval)
[824e488]327
[3bdbfcc]328        self.inner_marker.set(xdata=[self.sign * numpy.fabs(self._inner_mouse_x)],
[32c0841]329                              ydata=[0])
[824e488]330        self.inner_circle.set_data(x, y)
[ef0c170]331
332    def save(self, ev):
333        """
334        Remember the roughness for this layer and the next so that we
335        can restore on Esc.
336        """
337        self._inner_save_x = self._inner_mouse_x
338        self._inner_save_y = self._inner_mouse_y
339
340    def moveend(self, ev):
[eba08f1a]341        """
[83f4445]342        Called after a dragging motion
[eba08f1a]343        """
[ef0c170]344        self.base.moveend(ev)
[824e488]345
[ef0c170]346    def restore(self):
347        """
348        Restore the roughness for this layer.
349        """
350        self._inner_mouse_x = self._inner_save_x
351        self._inner_mouse_y = self._inner_save_y
352
353    def move(self, x, y, ev):
354        """
355        Process move to a new position, making sure that the move is allowed.
356        """
357        self._inner_mouse_x = x
358        self._inner_mouse_y = y
359        self.base.base.update()
[824e488]360
[ef0c170]361    def set_cursor(self, x, y):
[eba08f1a]362        """
[824e488]363        draw the ring given x, y value
[eba08f1a]364        """
[ef0c170]365        self.move(x, y, None)
366        self.update()
[824e488]367
[3bdbfcc]368    def getParams(self):
[eba08f1a]369        """
[83f4445]370        Store a copy of values of parameters of the slicer into a dictionary.
371        :return params: the dictionary created
[eba08f1a]372        """
[ef0c170]373        params = {}
[3bdbfcc]374        params["radius"] = numpy.fabs(self._inner_mouse_x)
[ef0c170]375        return params
[824e488]376
[3bdbfcc]377    def setParams(self, params):
[eba08f1a]378        """
[824e488]379        Receive a dictionary and reset the slicer with values contained
[83f4445]380        in the values of the dictionary.
[824e488]381
382        :param params: a dictionary containing name of slicer parameters and
[eba08f1a]383            values the user assigned to the slicer.
[824e488]384
[eba08f1a]385        """
[824e488]386        x = params["radius"]
[ef0c170]387        self.set_cursor(x, self._inner_mouse_y)
[824e488]388
[c5874f2]389class CircularMask(_BaseInteractor):
390    """
[824e488]391     Draw a ring Given a radius
[c5874f2]392    """
[32c0841]393    def __init__(self, base, axes, color='grey', zorder=3, side=None):
[83f4445]394        """
395        :param: the color of the line that defined the ring
396        :param r: the radius of the ring
[824e488]397        :param sign: the direction of motion the the marker
[83f4445]398        """
[c5874f2]399        _BaseInteractor.__init__(self, base, axes, color=color)
400        self.markers = []
401        self.axes = axes
[32c0841]402        self.base = base
[c5874f2]403        self.is_inside = side
[3bdbfcc]404        self.qmax = min(numpy.fabs(self.base.data.xmax),
405                        numpy.fabs(self.base.data.xmin))  # must be positive
[c5874f2]406        self.connect = self.base.connect
[824e488]407
408        # Cursor position of Rings (Left(-1) or Right(1))
[32c0841]409        self.xmaxd = self.base.data.xmax
410        self.xmind = self.base.data.xmin
[c5874f2]411
[32c0841]412        if (self.xmaxd + self.xmind) > 0:
413            self.sign = 1
[c5874f2]414        else:
[32c0841]415            self.sign = -1
[c5874f2]416        # Inner circle
[3bdbfcc]417        self.outer_circle = RingInteractor(self, self.axes, 'blue',
[824e488]418                                           zorder=zorder + 1, r=self.qmax / 1.8,
419                                           sign=self.sign)
[32c0841]420        self.outer_circle.qmax = self.qmax * 1.2
[c5874f2]421        self.update()
422        self._post_data()
[824e488]423
[c5874f2]424    def set_layer(self, n):
425        """
[83f4445]426        Allow adding plot to the same panel
427        :param n: the number of layer
[c5874f2]428        """
429        self.layernum = n
430        self.update()
[824e488]431
[c5874f2]432    def clear(self):
433        """
[83f4445]434        Clear the slicer and all connected events related to this slicer
[c5874f2]435        """
436        self.clear_markers()
437        self.outer_circle.clear()
438        self.base.connect.clearall()
[824e488]439
[c5874f2]440    def update(self):
441        """
[83f4445]442        Respond to changes in the model by recalculating the profiles and
443        resetting the widgets.
[c5874f2]444        """
[824e488]445        # Update locations
[c5874f2]446        self.outer_circle.update()
[3bdbfcc]447        self._post_data()
[c5874f2]448        out = self._post_data()
449        return out
450
451    def save(self, ev):
452        """
[83f4445]453        Remember the roughness for this layer and the next so that we
454        can restore on Esc.
[c5874f2]455        """
456        self.outer_circle.save(ev)
457
458    def _post_data(self):
459        """
[83f4445]460        Uses annulus parameters to plot averaged data into 1D data.
[824e488]461
462        :param nbins: the number of points to plot
463
[c5874f2]464        """
[824e488]465        # Data to average
[c5874f2]466        data = self.base.data
[824e488]467
[c5874f2]468        # If we have no data, just return
[3bdbfcc]469        if data is None:
[c5874f2]470            return
[824e488]471        mask = data.mask
[b699768]472        from sas.sascalc.dataloader.manipulations import Ringcut
[824e488]473
[32c0841]474        rmin = 0
[3bdbfcc]475        rmax = numpy.fabs(self.outer_circle.get_radius())
[c5874f2]476
[3bdbfcc]477        # Create the data1D Q average of data2D
[824e488]478        mask = Ringcut(r_min=rmin, r_max=rmax)
[c5874f2]479
480        if self.is_inside:
[32c0841]481            out = (mask(data) == False)
[c5874f2]482        else:
483            out = (mask(data))
[824e488]484        return out
485
[c5874f2]486
487    def moveend(self, ev):
488        """
[83f4445]489        Called when any dragging motion ends.
490        Post an event (type =SlicerParameterEvent)
491        to plotter 2D with a copy  slicer parameters
492        Call  _post_data method
[c5874f2]493        """
494        self.base.thaw_axes()
495        # create a 1D data plot
496        self._post_data()
[824e488]497
[c5874f2]498    def restore(self):
499        """
500        Restore the roughness for this layer.
501        """
502        self.outer_circle.restore()
503
504    def move(self, x, y, ev):
505        """
506        Process move to a new position, making sure that the move is allowed.
507        """
508        pass
[824e488]509
[c5874f2]510    def set_cursor(self, x, y):
511        pass
[824e488]512
[3bdbfcc]513    def getParams(self):
[c5874f2]514        """
[83f4445]515        Store a copy of values of parameters of the slicer into a dictionary.
[824e488]516
[83f4445]517        :return params: the dictionary created
[824e488]518
[c5874f2]519        """
520        params = {}
[3bdbfcc]521        params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x)
[c5874f2]522        return params
[824e488]523
[3bdbfcc]524    def setParams(self, params):
[c5874f2]525        """
[824e488]526        Receive a dictionary and reset the slicer with values contained
[83f4445]527        in the values of the dictionary.
[824e488]528
529        :param params: a dictionary containing name of slicer parameters and
[c5874f2]530            values the user assigned to the slicer.
531        """
[3bdbfcc]532        outer = numpy.fabs(params["outer_radius"])
533        # Update the picture
[c5874f2]534        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
[3bdbfcc]535        # Post the data given the nbins entered by the user
[c5874f2]536        self._post_data()
[824e488]537
[c5874f2]538    def draw(self):
539        self.base.update()
[824e488]540
Note: See TracBrowser for help on using the repository browser.