source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/SectorSlicer.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: 18.6 KB
Line 
1"""
2    Sector interactor
3"""
4import math
5from PyQt4 import QtGui
6from PyQt4 import QtCore
7
8from BaseInteractor import _BaseInteractor
9from sas.sasgui.guiframe.dataFitting import Data1D
10import sas.qtgui.GuiUtils as GuiUtils
11from sas.qtgui.SlicerModel import SlicerModel
12
13
14class SectorInteractor(_BaseInteractor, SlicerModel):
15    """
16    Draw a sector slicer.Allow to performQ averaging on data 2D
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        # Class initialization
23        self.markers = []
24        self.axes = axes
25        self._item = item
26        # Connect the plot to event
27        self.connect = self.base.connect
28
29        # Compute qmax limit to reset the graph
30        x = math.pow(max(self.base.data.xmax,
31                         math.fabs(self.base.data.xmin)), 2)
32        y = math.pow(max(self.base.data.ymax,
33                         math.fabs(self.base.data.ymin)), 2)
34        self.qmax = math.sqrt(x + y)
35        # Number of points on the plot
36        self.nbins = 20
37        # Angle of the middle line
38        self.theta2 = math.pi / 3
39        # Absolute value of the Angle between the middle line and any side line
40        self.phi = math.pi / 12
41        # Middle line
42        self.main_line = LineInteractor(self, self.axes, color='blue',
43                                        zorder=zorder, r=self.qmax,
44                                        theta=self.theta2)
45        self.main_line.qmax = self.qmax
46        # Right Side line
47        self.right_line = SideInteractor(self, self.axes, color='black',
48                                         zorder=zorder, r=self.qmax,
49                                         phi=-1 * self.phi, theta2=self.theta2)
50        self.right_line.qmax = self.qmax
51        # Left Side line
52        self.left_line = SideInteractor(self, self.axes, color='black',
53                                        zorder=zorder, r=self.qmax,
54                                        phi=self.phi, theta2=self.theta2)
55        self.left_line.qmax = self.qmax
56        # draw the sector
57        self.update()
58        self._post_data()
59        self.setModelFromParams()
60
61    def set_layer(self, n):
62        """
63         Allow adding plot to the same panel
64        :param n: the number of layer
65        """
66        self.layernum = n
67        self.update()
68
69    def clear(self):
70        """
71        Clear the slicer and all connected events related to this slicer
72        """
73        self.clear_markers()
74        self.main_line.clear()
75        self.left_line.clear()
76        self.right_line.clear()
77        self.base.connect.clearall()
78
79    def update(self):
80        """
81        Respond to changes in the model by recalculating the profiles and
82        resetting the widgets.
83        """
84        # Update locations
85        # Check if the middle line was dragged and
86        # update the picture accordingly
87        if self.main_line.has_move:
88            self.main_line.update()
89            self.right_line.update(delta=-self.left_line.phi / 2,
90                                   mline=self.main_line.theta)
91            self.left_line.update(delta=self.left_line.phi / 2,
92                                  mline=self.main_line.theta)
93        # Check if the left side has moved and update the slicer accordingly
94        if self.left_line.has_move:
95            self.main_line.update()
96            self.left_line.update(phi=None, delta=None, mline=self.main_line,
97                                  side=True, left=True)
98            self.right_line.update(phi=self.left_line.phi, delta=None,
99                                   mline=self.main_line, side=True,
100                                   left=False, right=True)
101        # Check if the right side line has moved and update the slicer accordingly
102        if self.right_line.has_move:
103            self.main_line.update()
104            self.right_line.update(phi=None, delta=None, mline=self.main_line,
105                                   side=True, left=False, right=True)
106            self.left_line.update(phi=self.right_line.phi, delta=None,
107                                  mline=self.main_line, side=True, left=False)
108
109    def save(self, ev):
110        """
111        Remember the roughness for this layer and the next so that we
112        can restore on Esc.
113        """
114        self.main_line.save(ev)
115        self.right_line.save(ev)
116        self.left_line.save(ev)
117
118    def _post_data(self, nbins=None):
119        """
120        compute sector averaging of data2D into data1D
121
122        :param nbins: the number of point to plot for the average 1D data
123        """
124        # Get the data2D to average
125        data = self.base.data
126        # If we have no data, just return
127        if data == None:
128            return
129        # Averaging
130        from sas.sascalc.dataloader.manipulations import SectorQ
131        radius = self.qmax
132        phimin = -self.left_line.phi + self.main_line.theta
133        phimax = self.left_line.phi + self.main_line.theta
134        if nbins == None:
135            nbins = 20
136        sect = SectorQ(r_min=0.0, r_max=radius,
137                       phi_min=phimin + math.pi,
138                       phi_max=phimax + math.pi, nbins=nbins)
139
140        sector = sect(self.base.data)
141        # Create 1D data resulting from average
142
143        if hasattr(sector, "dxl"):
144            dxl = sector.dxl
145        else:
146            dxl = None
147        if hasattr(sector, "dxw"):
148            dxw = sector.dxw
149        else:
150            dxw = None
151        new_plot = Data1D(x=sector.x, y=sector.y, dy=sector.dy, dx=sector.dx)
152        new_plot.dxl = dxl
153        new_plot.dxw = dxw
154        new_plot.name = "SectorQ" + "(" + self.base.data.name + ")"
155        new_plot.title = "SectorQ" + "(" + self.base.data.name + ")"
156        new_plot.source = self.base.data.source
157        new_plot.interactive = True
158        new_plot.detector = self.base.data.detector
159        # If the data file does not tell us what the axes are, just assume them.
160        new_plot.xaxis("\\rm{Q}", "A^{-1}")
161        new_plot.yaxis("\\rm{Intensity}", "cm^{-1}")
162        if hasattr(data, "scale") and data.scale == 'linear' and \
163                self.base.data.name.count("Residuals") > 0:
164            new_plot.ytransform = 'y'
165            new_plot.yaxis("\\rm{Residuals} ", "/")
166
167        new_plot.group_id = "2daverage" + self.base.data.name
168        new_plot.id = "SectorQ" + self.base.data.name
169        new_plot.is_data = True
170        variant_plot = QtCore.QVariant(new_plot)
171        GuiUtils.updateModelItemWithPlot(self._item, variant_plot, new_plot.id)
172
173        if self.update_model:
174            self.setModelFromParams()
175        self.draw()
176
177    def moveend(self, ev):
178        """
179        Called a dragging motion ends.Get slicer event
180        """
181        # Post parameters
182        self._post_data(self.nbins)
183
184    def restore(self):
185        """
186        Restore the roughness for this layer.
187        """
188        self.main_line.restore()
189        self.left_line.restore()
190        self.right_line.restore()
191
192    def move(self, x, y, ev):
193        """
194        Process move to a new position, making sure that the move is allowed.
195        """
196        pass
197
198    def set_cursor(self, x, y):
199        """
200        """
201        pass
202
203    def getParams(self):
204        """
205        Store a copy of values of parameters of the slicer into a dictionary.
206        :return params: the dictionary created
207        """
208        params = {}
209        # Always make sure that the left and the right line are at phi
210        # angle of the middle line
211        if math.fabs(self.left_line.phi) != math.fabs(self.right_line.phi):
212            msg = "Phi left and phi right are different"
213            msg += " %f, %f" % (self.left_line.phi, self.right_line.phi)
214            raise ValueError, msg
215        params["Phi [deg]"] = self.main_line.theta * 180 / math.pi
216        params["Delta_Phi [deg]"] = math.fabs(self.left_line.phi * 180 / math.pi)
217        params["nbins"] = self.nbins
218        return params
219
220    def setParams(self, params):
221        """
222        Receive a dictionary and reset the slicer with values contained
223        in the values of the dictionary.
224
225        :param params: a dictionary containing name of slicer parameters and
226            values the user assigned to the slicer.
227        """
228        main = params["Phi [deg]"] * math.pi / 180
229        phi = math.fabs(params["Delta_Phi [deg]"] * math.pi / 180)
230        self.nbins = int(params["nbins"])
231        self.main_line.theta = main
232        # Reset the slicer parameters
233        self.main_line.update()
234        self.right_line.update(phi=phi, delta=None, mline=self.main_line,
235                               side=True, right=True)
236        self.left_line.update(phi=phi, delta=None,
237                              mline=self.main_line, side=True)
238        # Post the new corresponding data
239        self._post_data(nbins=self.nbins)
240
241    def draw(self):
242        """
243        """
244        self.base.draw()
245
246
247class SideInteractor(_BaseInteractor):
248    """
249    Draw an oblique line
250
251    :param phi: the phase between the middle line and one side line
252    :param theta2: the angle between the middle line and x- axis
253
254    """
255    def __init__(self, base, axes, color='black', zorder=5, r=1.0,
256                 phi=math.pi / 4, theta2=math.pi / 3):
257        """
258        """
259        _BaseInteractor.__init__(self, base, axes, color=color)
260        # Initialize the class
261        self.markers = []
262        self.axes = axes
263        # compute the value of the angle between the current line and
264        # the x-axis
265        self.save_theta = theta2 + phi
266        self.theta = theta2 + phi
267        # the value of the middle line angle with respect to the x-axis
268        self.theta2 = theta2
269        # Radius to find polar coordinates this line's endpoints
270        self.radius = r
271        # phi is the phase between the current line and the middle line
272        self.phi = phi
273        # End points polar coordinates
274        x1 = self.radius * math.cos(self.theta)
275        y1 = self.radius * math.sin(self.theta)
276        x2 = -1 * self.radius * math.cos(self.theta)
277        y2 = -1 * self.radius * math.sin(self.theta)
278        # Defining a new marker
279        self.inner_marker = self.axes.plot([x1 / 2.5], [y1 / 2.5], linestyle='',
280                                           marker='s', markersize=10,
281                                           color=self.color, alpha=0.6,
282                                           pickradius=5, label="pick",
283                                           zorder=zorder, visible=True)[0]
284
285        # Defining the current line
286        self.line = self.axes.plot([x1, x2], [y1, y2],
287                                   linestyle='-', marker='',
288                                   color=self.color, visible=True)[0]
289        # Flag to differentiate the left line from the right line motion
290        self.left_moving = False
291        # Flag to define a motion
292        self.has_move = False
293        # connecting markers and draw the picture
294        self.connect_markers([self.inner_marker, self.line])
295
296    def set_layer(self, n):
297        """
298        Allow adding plot to the same panel
299        :param n: the number of layer
300        """
301        self.layernum = n
302        self.update()
303
304    def clear(self):
305        """
306        Clear the slicer and all connected events related to this slicer
307        """
308        self.clear_markers()
309        try:
310            self.line.remove()
311            self.inner_marker.remove()
312        except:
313            # Old version of matplotlib
314            for item in range(len(self.axes.lines)):
315                del self.axes.lines[0]
316
317    def update(self, phi=None, delta=None, mline=None,
318               side=False, left=False, right=False):
319        """
320        Draw oblique line
321
322        :param phi: the phase between the middle line and the current line
323        :param delta: phi/2 applied only when the mline was moved
324
325        """
326        self.left_moving = left
327        theta3 = 0
328        if phi != None:
329            self.phi = phi
330        if delta == None:
331            delta = 0
332        if  right:
333            self.phi = -1 * math.fabs(self.phi)
334            #delta=-delta
335        else:
336            self.phi = math.fabs(self.phi)
337        if side:
338            self.theta = mline.theta + self.phi
339
340        if mline != None:
341            if delta != 0:
342                self.theta2 = mline + delta
343            else:
344                self.theta2 = mline.theta
345        if delta == 0:
346            theta3 = self.theta + delta
347        else:
348            theta3 = self.theta2 + delta
349        x1 = self.radius * math.cos(theta3)
350        y1 = self.radius * math.sin(theta3)
351        x2 = -1 * self.radius * math.cos(theta3)
352        y2 = -1 * self.radius * math.sin(theta3)
353        self.inner_marker.set(xdata=[x1 / 2.5], ydata=[y1 / 2.5])
354        self.line.set(xdata=[x1, x2], ydata=[y1, y2])
355
356    def save(self, ev):
357        """
358        Remember the roughness for this layer and the next so that we
359        can restore on Esc.
360        """
361        self.save_theta = self.theta
362
363    def moveend(self, ev):
364        """
365        """
366        self.has_move = False
367        self.base.moveend(ev)
368
369    def restore(self):
370        """
371        Restore the roughness for this layer.
372        """
373        self.theta = self.save_theta
374
375    def move(self, x, y, ev):
376        """
377        Process move to a new position, making sure that the move is allowed.
378        """
379        self.theta = math.atan2(y, x)
380        self.has_move = True
381        if not self.left_moving:
382            if  self.theta2 - self.theta <= 0 and self.theta2 > 0:
383                self.restore()
384                return
385            elif self.theta2 < 0 and self.theta < 0 and \
386                self.theta - self.theta2 >= 0:
387                self.restore()
388                return
389            elif  self.theta2 < 0 and self.theta > 0 and \
390                (self.theta2 + 2 * math.pi - self.theta) >= math.pi / 2:
391                self.restore()
392                return
393            elif  self.theta2 < 0 and self.theta < 0 and \
394                (self.theta2 - self.theta) >= math.pi / 2:
395                self.restore()
396                return
397            elif self.theta2 > 0 and (self.theta2 - self.theta >= math.pi / 2 or \
398                (self.theta2 - self.theta >= math.pi / 2)):
399                self.restore()
400                return
401        else:
402            if  self.theta < 0 and (self.theta + math.pi * 2 - self.theta2) <= 0:
403                self.restore()
404                return
405            elif self.theta2 < 0 and (self.theta - self.theta2) <= 0:
406                self.restore()
407                return
408            elif  self.theta > 0 and self.theta - self.theta2 <= 0:
409                self.restore()
410                return
411            elif self.theta - self.theta2 >= math.pi / 2 or  \
412                ((self.theta + math.pi * 2 - self.theta2) >= math.pi / 2 and \
413                 self.theta < 0 and self.theta2 > 0):
414                self.restore()
415                return
416
417        self.phi = math.fabs(self.theta2 - self.theta)
418        if self.phi > math.pi:
419            self.phi = 2 * math.pi - math.fabs(self.theta2 - self.theta)
420        self.base.base.update()
421
422    def set_cursor(self, x, y):
423        """
424        """
425        self.move(x, y, None)
426        self.update()
427
428    def getParams(self):
429        """
430        """
431        params = {}
432        params["radius"] = self.radius
433        params["theta"] = self.theta
434        return params
435
436    def setParams(self, params):
437        """
438        """
439        x = params["radius"]
440        self.set_cursor(x, None)
441
442
443class LineInteractor(_BaseInteractor):
444    """
445    Select an annulus through a 2D plot
446    """
447    def __init__(self, base, axes, color='black',
448                 zorder=5, r=1.0, theta=math.pi / 4):
449        """
450        """
451        _BaseInteractor.__init__(self, base, axes, color=color)
452
453        self.markers = []
454        self.axes = axes
455        self.save_theta = theta
456        self.theta = theta
457        self.radius = r
458        self.scale = 10.0
459        # Inner circle
460        x1 = self.radius * math.cos(self.theta)
461        y1 = self.radius * math.sin(self.theta)
462        x2 = -1 * self.radius * math.cos(self.theta)
463        y2 = -1 * self.radius * math.sin(self.theta)
464        # Inner circle marker
465        self.inner_marker = self.axes.plot([x1 / 2.5], [y1 / 2.5], linestyle='',
466                                           marker='s', markersize=10,
467                                           color=self.color, alpha=0.6,
468                                           pickradius=5, label="pick",
469                                           zorder=zorder,
470                                           visible=True)[0]
471        self.line = self.axes.plot([x1, x2], [y1, y2],
472                                   linestyle='-', marker='',
473                                   color=self.color, visible=True)[0]
474        self.npts = 20
475        self.has_move = False
476        self.connect_markers([self.inner_marker, self.line])
477        self.update()
478
479    def set_layer(self, n):
480        """
481        """
482        self.layernum = n
483        self.update()
484
485    def clear(self):
486        """
487        """
488        self.clear_markers()
489        try:
490            self.inner_marker.remove()
491            self.line.remove()
492        except:
493            # Old version of matplotlib
494            for item in range(len(self.axes.lines)):
495                del self.axes.lines[0]
496
497    def update(self, theta=None):
498        """
499        Draw the new roughness on the graph.
500        """
501
502        if theta != None:
503            self.theta = theta
504        x1 = self.radius * math.cos(self.theta)
505        y1 = self.radius * math.sin(self.theta)
506        x2 = -1 * self.radius * math.cos(self.theta)
507        y2 = -1 * self.radius * math.sin(self.theta)
508
509        self.inner_marker.set(xdata=[x1 / 2.5], ydata=[y1 / 2.5])
510        self.line.set(xdata=[x1, x2], ydata=[y1, y2])
511
512    def save(self, ev):
513        """
514        Remember the roughness for this layer and the next so that we
515        can restore on Esc.
516        """
517        self.save_theta = self.theta
518
519    def moveend(self, ev):
520        """
521        """
522        self.has_move = False
523        self.base.moveend(ev)
524
525    def restore(self):
526        """
527        Restore the roughness for this layer.
528        """
529        self.theta = self.save_theta
530
531    def move(self, x, y, ev):
532        """
533        Process move to a new position, making sure that the move is allowed.
534        """
535        self.theta = math.atan2(y, x)
536        self.has_move = True
537        self.base.base.update()
538
539    def set_cursor(self, x, y):
540        """
541        """
542        self.move(x, y, None)
543        self.update()
544
545    def getParams(self):
546        """
547        """
548        params = {}
549        params["radius"] = self.radius
550        params["theta"] = self.theta
551        return params
552
553    def setParams(self, params):
554        """
555        """
556        x = params["radius"]
557        self.set_cursor(x, None)
Note: See TracBrowser for help on using the repository browser.