source: sasview/src/sas/qtgui/Plotting/Slicers/SectorSlicer.py @ 895e7359

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

Converted more syntax not covered by 2to3

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