source: sasview/src/sas/qtgui/Plotting/Slicers/AnnulusSlicer.py @ 7fb471d

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

Converted more syntax not covered by 2to3

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