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

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 965fbd8 was 3bdbfcc, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Reimplementation of the slicer functionality

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