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

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 b9e89d5 was cee5c78, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Converted more syntax not covered by 2to3

  • 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"
[cee5c78]149        GuiUtils.updateModelItemWithPlot(self._item, new_plot, new_plot.id)
[116260a]150        self.base.manager.communicator.plotUpdateSignal.emit([new_plot])
[3bdbfcc]151
152        if self.update_model:
153            self.setModelFromParams()
154        self.draw()
155
[161713c]156    def validate(self, param_name, param_value):
157        """
158        Test the proposed new value "value" for row "row" of parameters
159        """
160        MIN_DIFFERENCE = 0.01
161        isValid = True
162
163        if param_name == 'inner_radius':
164            # First, check the closeness
165            if numpy.fabs(param_value - self.getParams()['outer_radius']) < MIN_DIFFERENCE:
166                print("Inner and outer radii too close. Please adjust.")
167                isValid = False
168            elif param_value > self.qmax:
169                print("Inner radius exceeds maximum range. Please adjust.")
170                isValid = False
171        elif param_name == 'outer_radius':
172            # First, check the closeness
173            if numpy.fabs(param_value - self.getParams()['inner_radius']) < MIN_DIFFERENCE:
174                print("Inner and outer radii too close. Please adjust.")
175                isValid = False
176            elif param_value > self.qmax:
177                print("Outer radius exceeds maximum range. Please adjust.")
178                isValid = False
179        elif param_name == 'nbins':
180            # Can't be 0
181            if param_value < 1:
182                print("Number of bins cannot be less than or equal to 0. Please adjust.")
183                isValid = False
184
185        return isValid
[824e488]186
[ef0c170]187    def moveend(self, ev):
[eba08f1a]188        """
[83f4445]189        Called when any dragging motion ends.
[3bdbfcc]190        Redraw the plot with new parameters.
[eba08f1a]191        """
[3bdbfcc]192        self._post_data(self.nbins)
[824e488]193
[ef0c170]194    def restore(self):
195        """
196        Restore the roughness for this layer.
197        """
198        self.inner_circle.restore()
199        self.outer_circle.restore()
200
201    def move(self, x, y, ev):
202        """
203        Process move to a new position, making sure that the move is allowed.
204        """
205        pass
[824e488]206
[ef0c170]207    def set_cursor(self, x, y):
208        pass
[824e488]209
[3bdbfcc]210    def getParams(self):
[eba08f1a]211        """
[83f4445]212        Store a copy of values of parameters of the slicer into a dictionary.
213        :return params: the dictionary created
[eba08f1a]214        """
[ef0c170]215        params = {}
[3bdbfcc]216        params["inner_radius"] = numpy.fabs(self.inner_circle._inner_mouse_x)
217        params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x)
[ef0c170]218        params["nbins"] = self.nbins
219        return params
[824e488]220
[3bdbfcc]221    def setParams(self, params):
[eba08f1a]222        """
[824e488]223        Receive a dictionary and reset the slicer with values contained
[83f4445]224        in the values of the dictionary.
[824e488]225
226        :param params: a dictionary containing name of slicer parameters and
[eba08f1a]227            values the user assigned to the slicer.
228        """
[3bdbfcc]229        inner = numpy.fabs(params["inner_radius"])
230        outer = numpy.fabs(params["outer_radius"])
[ef0c170]231        self.nbins = int(params["nbins"])
[3bdbfcc]232        # Update the picture
[ef0c170]233        self.inner_circle.set_cursor(inner, self.inner_circle._inner_mouse_y)
234        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
[3bdbfcc]235        # Post the data given the nbins entered by the user
[5554566]236        self._post_data(self.nbins)
[824e488]237
[ef0c170]238    def draw(self):
[83f4445]239        """
240        """
[ef0c170]241        self.base.draw()
242
[824e488]243
[ef0c170]244class RingInteractor(_BaseInteractor):
245    """
[824e488]246     Draw a ring Given a radius
[ef0c170]247    """
[32c0841]248    def __init__(self, base, axes, color='black', zorder=5, r=1.0, sign=1):
[83f4445]249        """
250        :param: the color of the line that defined the ring
251        :param r: the radius of the ring
[824e488]252        :param sign: the direction of motion the the marker
253
[83f4445]254        """
[ef0c170]255        _BaseInteractor.__init__(self, base, axes, color=color)
256        self.markers = []
257        self.axes = axes
[eba08f1a]258        # Current radius of the ring
[ef0c170]259        self._inner_mouse_x = r
[824e488]260        # Value of the center of the ring
[ef0c170]261        self._inner_mouse_y = 0
[eba08f1a]262        # previous value of that radius
[824e488]263        self._inner_save_x = r
264        # Save value of the center of the ring
265        self._inner_save_y = 0
266        # Class instantiating RingIterator class
[32c0841]267        self.base = base
[824e488]268        # the direction of the motion of the marker
[32c0841]269        self.sign = sign
[824e488]270        # # Create a marker
[3bdbfcc]271        # Inner circle marker
272        x_value = [self.sign * numpy.fabs(self._inner_mouse_x)]
273        self.inner_marker = self.axes.plot(x_value, [0], linestyle='',
274                                           marker='s', markersize=10,
275                                           color=self.color, alpha=0.6,
276                                           pickradius=5, label="pick",
277                                           zorder=zorder,
278                                           visible=True)[0]
[824e488]279        # Draw a circle
280        [self.inner_circle] = self.axes.plot([], [], linestyle='-', marker='', color=self.color)
[3bdbfcc]281        # The number of points that make the ring line
[e4032d64]282        self.npts = 40
[824e488]283
[ef0c170]284        self.connect_markers([self.inner_marker])
285        self.update()
286
287    def set_layer(self, n):
[eba08f1a]288        """
[83f4445]289        Allow adding plot to the same panel
[824e488]290
[83f4445]291        :param n: the number of layer
[824e488]292
[eba08f1a]293        """
[ef0c170]294        self.layernum = n
295        self.update()
[824e488]296
[ef0c170]297    def clear(self):
[eba08f1a]298        """
[83f4445]299        Clear the slicer and all connected events related to this slicer
[eba08f1a]300        """
[ef0c170]301        self.clear_markers()
[3bdbfcc]302        self.inner_marker.remove()
303        self.inner_circle.remove()
[824e488]304
[ef0c170]305    def get_radius(self):
[eba08f1a]306        """
[83f4445]307        :return self._inner_mouse_x: the current radius of the ring
[eba08f1a]308        """
[ef0c170]309        return self._inner_mouse_x
[824e488]310
[ef0c170]311    def update(self):
312        """
[83f4445]313        Draw the new roughness on the graph.
[ef0c170]314        """
315        # Plot inner circle
316        x = []
317        y = []
318        for i in range(self.npts):
[3bdbfcc]319            phi = 2.0 * numpy.pi / (self.npts - 1) * i
[824e488]320
[3bdbfcc]321            xval = 1.0 * self._inner_mouse_x * numpy.cos(phi)
322            yval = 1.0 * self._inner_mouse_x * numpy.sin(phi)
[824e488]323
[ef0c170]324            x.append(xval)
325            y.append(yval)
[824e488]326
[3bdbfcc]327        self.inner_marker.set(xdata=[self.sign * numpy.fabs(self._inner_mouse_x)],
[32c0841]328                              ydata=[0])
[824e488]329        self.inner_circle.set_data(x, y)
[ef0c170]330
331    def save(self, ev):
332        """
333        Remember the roughness for this layer and the next so that we
334        can restore on Esc.
335        """
336        self._inner_save_x = self._inner_mouse_x
337        self._inner_save_y = self._inner_mouse_y
338
339    def moveend(self, ev):
[eba08f1a]340        """
[83f4445]341        Called after a dragging motion
[eba08f1a]342        """
[ef0c170]343        self.base.moveend(ev)
[824e488]344
[ef0c170]345    def restore(self):
346        """
347        Restore the roughness for this layer.
348        """
349        self._inner_mouse_x = self._inner_save_x
350        self._inner_mouse_y = self._inner_save_y
351
352    def move(self, x, y, ev):
353        """
354        Process move to a new position, making sure that the move is allowed.
355        """
356        self._inner_mouse_x = x
357        self._inner_mouse_y = y
358        self.base.base.update()
[824e488]359
[ef0c170]360    def set_cursor(self, x, y):
[eba08f1a]361        """
[824e488]362        draw the ring given x, y value
[eba08f1a]363        """
[ef0c170]364        self.move(x, y, None)
365        self.update()
[824e488]366
[3bdbfcc]367    def getParams(self):
[eba08f1a]368        """
[83f4445]369        Store a copy of values of parameters of the slicer into a dictionary.
370        :return params: the dictionary created
[eba08f1a]371        """
[ef0c170]372        params = {}
[3bdbfcc]373        params["radius"] = numpy.fabs(self._inner_mouse_x)
[ef0c170]374        return params
[824e488]375
[3bdbfcc]376    def setParams(self, params):
[eba08f1a]377        """
[824e488]378        Receive a dictionary and reset the slicer with values contained
[83f4445]379        in the values of the dictionary.
[824e488]380
381        :param params: a dictionary containing name of slicer parameters and
[eba08f1a]382            values the user assigned to the slicer.
[824e488]383
[eba08f1a]384        """
[824e488]385        x = params["radius"]
[ef0c170]386        self.set_cursor(x, self._inner_mouse_y)
[824e488]387
[c5874f2]388class CircularMask(_BaseInteractor):
389    """
[824e488]390     Draw a ring Given a radius
[c5874f2]391    """
[32c0841]392    def __init__(self, base, axes, color='grey', zorder=3, side=None):
[83f4445]393        """
394        :param: the color of the line that defined the ring
395        :param r: the radius of the ring
[824e488]396        :param sign: the direction of motion the the marker
[83f4445]397        """
[c5874f2]398        _BaseInteractor.__init__(self, base, axes, color=color)
399        self.markers = []
400        self.axes = axes
[32c0841]401        self.base = base
[c5874f2]402        self.is_inside = side
[3bdbfcc]403        self.qmax = min(numpy.fabs(self.base.data.xmax),
404                        numpy.fabs(self.base.data.xmin))  # must be positive
[c5874f2]405        self.connect = self.base.connect
[824e488]406
407        # Cursor position of Rings (Left(-1) or Right(1))
[32c0841]408        self.xmaxd = self.base.data.xmax
409        self.xmind = self.base.data.xmin
[c5874f2]410
[32c0841]411        if (self.xmaxd + self.xmind) > 0:
412            self.sign = 1
[c5874f2]413        else:
[32c0841]414            self.sign = -1
[c5874f2]415        # Inner circle
[3bdbfcc]416        self.outer_circle = RingInteractor(self, self.axes, 'blue',
[824e488]417                                           zorder=zorder + 1, r=self.qmax / 1.8,
418                                           sign=self.sign)
[32c0841]419        self.outer_circle.qmax = self.qmax * 1.2
[c5874f2]420        self.update()
421        self._post_data()
[824e488]422
[c5874f2]423    def set_layer(self, n):
424        """
[83f4445]425        Allow adding plot to the same panel
426        :param n: the number of layer
[c5874f2]427        """
428        self.layernum = n
429        self.update()
[824e488]430
[c5874f2]431    def clear(self):
432        """
[83f4445]433        Clear the slicer and all connected events related to this slicer
[c5874f2]434        """
435        self.clear_markers()
436        self.outer_circle.clear()
437        self.base.connect.clearall()
[824e488]438
[c5874f2]439    def update(self):
440        """
[83f4445]441        Respond to changes in the model by recalculating the profiles and
442        resetting the widgets.
[c5874f2]443        """
[824e488]444        # Update locations
[c5874f2]445        self.outer_circle.update()
[3bdbfcc]446        self._post_data()
[c5874f2]447        out = self._post_data()
448        return out
449
450    def save(self, ev):
451        """
[83f4445]452        Remember the roughness for this layer and the next so that we
453        can restore on Esc.
[c5874f2]454        """
455        self.outer_circle.save(ev)
456
457    def _post_data(self):
458        """
[83f4445]459        Uses annulus parameters to plot averaged data into 1D data.
[824e488]460
461        :param nbins: the number of points to plot
462
[c5874f2]463        """
[824e488]464        # Data to average
[c5874f2]465        data = self.base.data
[824e488]466
[c5874f2]467        # If we have no data, just return
[3bdbfcc]468        if data is None:
[c5874f2]469            return
[824e488]470        mask = data.mask
[b699768]471        from sas.sascalc.dataloader.manipulations import Ringcut
[824e488]472
[32c0841]473        rmin = 0
[3bdbfcc]474        rmax = numpy.fabs(self.outer_circle.get_radius())
[c5874f2]475
[3bdbfcc]476        # Create the data1D Q average of data2D
[824e488]477        mask = Ringcut(r_min=rmin, r_max=rmax)
[c5874f2]478
479        if self.is_inside:
[32c0841]480            out = (mask(data) == False)
[c5874f2]481        else:
482            out = (mask(data))
[824e488]483        return out
484
[c5874f2]485
486    def moveend(self, ev):
487        """
[83f4445]488        Called when any dragging motion ends.
489        Post an event (type =SlicerParameterEvent)
490        to plotter 2D with a copy  slicer parameters
491        Call  _post_data method
[c5874f2]492        """
493        self.base.thaw_axes()
494        # create a 1D data plot
495        self._post_data()
[824e488]496
[c5874f2]497    def restore(self):
498        """
499        Restore the roughness for this layer.
500        """
501        self.outer_circle.restore()
502
503    def move(self, x, y, ev):
504        """
505        Process move to a new position, making sure that the move is allowed.
506        """
507        pass
[824e488]508
[c5874f2]509    def set_cursor(self, x, y):
510        pass
[824e488]511
[3bdbfcc]512    def getParams(self):
[c5874f2]513        """
[83f4445]514        Store a copy of values of parameters of the slicer into a dictionary.
[824e488]515
[83f4445]516        :return params: the dictionary created
[824e488]517
[c5874f2]518        """
519        params = {}
[3bdbfcc]520        params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x)
[c5874f2]521        return params
[824e488]522
[3bdbfcc]523    def setParams(self, params):
[c5874f2]524        """
[824e488]525        Receive a dictionary and reset the slicer with values contained
[83f4445]526        in the values of the dictionary.
[824e488]527
528        :param params: a dictionary containing name of slicer parameters and
[c5874f2]529            values the user assigned to the slicer.
530        """
[3bdbfcc]531        outer = numpy.fabs(params["outer_radius"])
532        # Update the picture
[c5874f2]533        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
[3bdbfcc]534        # Post the data given the nbins entered by the user
[c5874f2]535        self._post_data()
[824e488]536
[c5874f2]537    def draw(self):
538        self.base.update()
[824e488]539
Note: See TracBrowser for help on using the repository browser.