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

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

Putting files in more ordered fashion

  • Property mode set to 100644
File size: 16.8 KB
Line 
1import numpy
2from PyQt4 import QtGui
3from PyQt4 import QtCore
4
5from BaseInteractor import _BaseInteractor
6from sas.sasgui.guiframe.dataFitting import Data1D
7import sas.qtgui.Utilities.GuiUtils as GuiUtils
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
59        :param n: the number of layer
60
61        """
62        self.layernum = n
63        self.update()
64
65    def clear(self):
66        """
67        Clear the slicer and all connected events related to this slicer
68        """
69        self.clear_markers()
70        self.outer_circle.clear()
71        self.inner_circle.clear()
72        self.base.connect.clearall()
73
74    def update(self):
75        """
76        Respond to changes in the model by recalculating the profiles and
77        resetting the widgets.
78        """
79        # Update locations
80        self.inner_circle.update()
81        self.outer_circle.update()
82
83    def save(self, ev):
84        """
85        Remember the roughness for this layer and the next so that we
86        can restore on Esc.
87        """
88        self.inner_circle.save(ev)
89        self.outer_circle.save(ev)
90
91    def _post_data(self, nbins=None):
92        """
93        Uses annulus parameters to plot averaged data into 1D data.
94
95        :param nbins: the number of points to plot
96
97        """
98        # Data to average
99        data = self.base.data
100        if data is None:
101            return
102
103        from sas.sascalc.dataloader.manipulations import Ring
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
109        # the default number will be nbins= 36
110        if nbins is None:
111            self.nbins = 36
112        else:
113            self.nbins = nbins
114        # Create the data1D Q average of data2D
115        sect = Ring(r_min=rmin, r_max=rmax, nbins=self.nbins)
116        sector = sect(self.base.data)
117
118        if hasattr(sector, "dxl"):
119            dxl = sector.dxl
120        else:
121            dxl = None
122        if hasattr(sector, "dxw"):
123            dxw = sector.dxw
124        else:
125            dxw = None
126        new_plot = Data1D(x=(sector.x - numpy.pi) * 180 / numpy.pi,
127                          y=sector.y, dy=sector.dy)
128        new_plot.dxl = dxl
129        new_plot.dxw = dxw
130        new_plot.name = "AnnulusPhi" + "(" + self.base.data.name + ")"
131        new_plot.title = "AnnulusPhi" + "(" + self.base.data.name + ")"
132
133        new_plot.source = self.base.data.source
134        new_plot.interactive = True
135        new_plot.detector = self.base.data.detector
136        # If the data file does not tell us what the axes are, just assume...
137        new_plot.xaxis("\\rm{\phi}", 'degrees')
138        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
139        if hasattr(data, "scale") and data.scale == 'linear' and \
140                self.base.data.name.count("Residuals") > 0:
141            new_plot.ytransform = 'y'
142            new_plot.yaxis("\\rm{Residuals} ", "/")
143
144        new_plot.group_id = "AnnulusPhi" + self.base.data.name
145        new_plot.id = "AnnulusPhi" + self.base.data.name
146        new_plot.is_data = True
147        new_plot.xtransform = "x"
148        new_plot.ytransform = "y"
149        variant_plot = QtCore.QVariant(new_plot)
150        GuiUtils.updateModelItemWithPlot(self._item, variant_plot, new_plot.id)
151        self.base.manager.communicator.plotUpdateSignal.emit([new_plot])
152
153        if self.update_model:
154            self.setModelFromParams()
155        self.draw()
156
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
187
188    def moveend(self, ev):
189        """
190        Called when any dragging motion ends.
191        Redraw the plot with new parameters.
192        """
193        self._post_data(self.nbins)
194
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
207
208    def set_cursor(self, x, y):
209        pass
210
211    def getParams(self):
212        """
213        Store a copy of values of parameters of the slicer into a dictionary.
214        :return params: the dictionary created
215        """
216        params = {}
217        params["inner_radius"] = numpy.fabs(self.inner_circle._inner_mouse_x)
218        params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x)
219        params["nbins"] = self.nbins
220        return params
221
222    def setParams(self, params):
223        """
224        Receive a dictionary and reset the slicer with values contained
225        in the values of the dictionary.
226
227        :param params: a dictionary containing name of slicer parameters and
228            values the user assigned to the slicer.
229        """
230        inner = numpy.fabs(params["inner_radius"])
231        outer = numpy.fabs(params["outer_radius"])
232        self.nbins = int(params["nbins"])
233        # Update the picture
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)
236        # Post the data given the nbins entered by the user
237        self._post_data(self.nbins)
238
239    def draw(self):
240        """
241        """
242        self.base.draw()
243
244
245class RingInteractor(_BaseInteractor):
246    """
247     Draw a ring Given a radius
248    """
249    def __init__(self, base, axes, color='black', zorder=5, r=1.0, sign=1):
250        """
251        :param: the color of the line that defined the ring
252        :param r: the radius of the ring
253        :param sign: the direction of motion the the marker
254
255        """
256        _BaseInteractor.__init__(self, base, axes, color=color)
257        self.markers = []
258        self.axes = axes
259        # Current radius of the ring
260        self._inner_mouse_x = r
261        # Value of the center of the ring
262        self._inner_mouse_y = 0
263        # previous value of that radius
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
268        self.base = base
269        # the direction of the motion of the marker
270        self.sign = sign
271        # # Create a marker
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]
280        # Draw a circle
281        [self.inner_circle] = self.axes.plot([], [], linestyle='-', marker='', color=self.color)
282        # The number of points that make the ring line
283        self.npts = 40
284
285        self.connect_markers([self.inner_marker])
286        self.update()
287
288    def set_layer(self, n):
289        """
290        Allow adding plot to the same panel
291
292        :param n: the number of layer
293
294        """
295        self.layernum = n
296        self.update()
297
298    def clear(self):
299        """
300        Clear the slicer and all connected events related to this slicer
301        """
302        self.clear_markers()
303        self.inner_marker.remove()
304        self.inner_circle.remove()
305
306    def get_radius(self):
307        """
308        :return self._inner_mouse_x: the current radius of the ring
309        """
310        return self._inner_mouse_x
311
312    def update(self):
313        """
314        Draw the new roughness on the graph.
315        """
316        # Plot inner circle
317        x = []
318        y = []
319        for i in range(self.npts):
320            phi = 2.0 * numpy.pi / (self.npts - 1) * i
321
322            xval = 1.0 * self._inner_mouse_x * numpy.cos(phi)
323            yval = 1.0 * self._inner_mouse_x * numpy.sin(phi)
324
325            x.append(xval)
326            y.append(yval)
327
328        self.inner_marker.set(xdata=[self.sign * numpy.fabs(self._inner_mouse_x)],
329                              ydata=[0])
330        self.inner_circle.set_data(x, y)
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):
341        """
342        Called after a dragging motion
343        """
344        self.base.moveend(ev)
345
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()
360
361    def set_cursor(self, x, y):
362        """
363        draw the ring given x, y value
364        """
365        self.move(x, y, None)
366        self.update()
367
368    def getParams(self):
369        """
370        Store a copy of values of parameters of the slicer into a dictionary.
371        :return params: the dictionary created
372        """
373        params = {}
374        params["radius"] = numpy.fabs(self._inner_mouse_x)
375        return params
376
377    def setParams(self, params):
378        """
379        Receive a dictionary and reset the slicer with values contained
380        in the values of the dictionary.
381
382        :param params: a dictionary containing name of slicer parameters and
383            values the user assigned to the slicer.
384
385        """
386        x = params["radius"]
387        self.set_cursor(x, self._inner_mouse_y)
388
389class CircularMask(_BaseInteractor):
390    """
391     Draw a ring Given a radius
392    """
393    def __init__(self, base, axes, color='grey', zorder=3, side=None):
394        """
395        :param: the color of the line that defined the ring
396        :param r: the radius of the ring
397        :param sign: the direction of motion the the marker
398        """
399        _BaseInteractor.__init__(self, base, axes, color=color)
400        self.markers = []
401        self.axes = axes
402        self.base = base
403        self.is_inside = side
404        self.qmax = min(numpy.fabs(self.base.data.xmax),
405                        numpy.fabs(self.base.data.xmin))  # must be positive
406        self.connect = self.base.connect
407
408        # Cursor position of Rings (Left(-1) or Right(1))
409        self.xmaxd = self.base.data.xmax
410        self.xmind = self.base.data.xmin
411
412        if (self.xmaxd + self.xmind) > 0:
413            self.sign = 1
414        else:
415            self.sign = -1
416        # Inner circle
417        self.outer_circle = RingInteractor(self, self.axes, 'blue',
418                                           zorder=zorder + 1, r=self.qmax / 1.8,
419                                           sign=self.sign)
420        self.outer_circle.qmax = self.qmax * 1.2
421        self.update()
422        self._post_data()
423
424    def set_layer(self, n):
425        """
426        Allow adding plot to the same panel
427        :param n: the number of layer
428        """
429        self.layernum = n
430        self.update()
431
432    def clear(self):
433        """
434        Clear the slicer and all connected events related to this slicer
435        """
436        self.clear_markers()
437        self.outer_circle.clear()
438        self.base.connect.clearall()
439
440    def update(self):
441        """
442        Respond to changes in the model by recalculating the profiles and
443        resetting the widgets.
444        """
445        # Update locations
446        self.outer_circle.update()
447        self._post_data()
448        out = self._post_data()
449        return out
450
451    def save(self, ev):
452        """
453        Remember the roughness for this layer and the next so that we
454        can restore on Esc.
455        """
456        self.outer_circle.save(ev)
457
458    def _post_data(self):
459        """
460        Uses annulus parameters to plot averaged data into 1D data.
461
462        :param nbins: the number of points to plot
463
464        """
465        # Data to average
466        data = self.base.data
467
468        # If we have no data, just return
469        if data is None:
470            return
471        mask = data.mask
472        from sas.sascalc.dataloader.manipulations import Ringcut
473
474        rmin = 0
475        rmax = numpy.fabs(self.outer_circle.get_radius())
476
477        # Create the data1D Q average of data2D
478        mask = Ringcut(r_min=rmin, r_max=rmax)
479
480        if self.is_inside:
481            out = (mask(data) == False)
482        else:
483            out = (mask(data))
484        return out
485
486
487    def moveend(self, ev):
488        """
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
493        """
494        self.base.thaw_axes()
495        # create a 1D data plot
496        self._post_data()
497
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
509
510    def set_cursor(self, x, y):
511        pass
512
513    def getParams(self):
514        """
515        Store a copy of values of parameters of the slicer into a dictionary.
516
517        :return params: the dictionary created
518
519        """
520        params = {}
521        params["outer_radius"] = numpy.fabs(self.outer_circle._inner_mouse_x)
522        return params
523
524    def setParams(self, params):
525        """
526        Receive a dictionary and reset the slicer with values contained
527        in the values of the dictionary.
528
529        :param params: a dictionary containing name of slicer parameters and
530            values the user assigned to the slicer.
531        """
532        outer = numpy.fabs(params["outer_radius"])
533        # Update the picture
534        self.outer_circle.set_cursor(outer, self.outer_circle._inner_mouse_y)
535        # Post the data given the nbins entered by the user
536        self._post_data()
537
538    def draw(self):
539        self.base.update()
540
Note: See TracBrowser for help on using the repository browser.