source: sasview/src/sas/qtgui/Plotting/Slicers/BoxSlicer.py

ESS_GUI
Last change on this file was bf7f025, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Modified the box slicer group_id so it doesn't clash with circular.
SASVIEW-1225

  • Property mode set to 100644
File size: 16.6 KB
Line 
1import numpy
2
3from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor
4from sas.qtgui.Plotting.PlotterData import Data1D
5import sas.qtgui.Utilities.GuiUtils as GuiUtils
6from sas.qtgui.Plotting.SlicerModel import SlicerModel
7
8
9class BoxInteractor(BaseInteractor, SlicerModel):
10    """
11    BoxInteractor define a rectangle that return data1D average of Data2D
12    in a rectangle area defined by -x, x ,y, -y
13    """
14    def __init__(self, base, axes, item=None, color='black', zorder=3):
15        BaseInteractor.__init__(self, base, axes, color=color)
16        SlicerModel.__init__(self)
17        # Class initialization
18        self.markers = []
19        self.axes = axes
20        self._item = item
21        #connecting artist
22        self.connect = self.base.connect
23        # which direction is the preferred interaction direction
24        self.direction = None
25        # determine x y  values
26        self.x = 0.5 * min(numpy.fabs(self.base.data.xmax),
27                           numpy.fabs(self.base.data.xmin))
28        self.y = 0.5 * min(numpy.fabs(self.base.data.xmax),
29                           numpy.fabs(self.base.data.xmin))
30        # when reach qmax reset the graph
31        self.qmax = max(self.base.data.xmax, self.base.data.xmin,
32                        self.base.data.ymax, self.base.data.ymin)
33        # Number of points on the plot
34        self.nbins = 30
35        # If True, I(|Q|) will be return, otherwise,
36        # negative q-values are allowed
37        self.fold = True
38        # reference of the current  Slab averaging
39        self.averager = None
40        # Create vertical and horizaontal lines for the rectangle
41        self.vertical_lines = VerticalLines(self,
42                                            self.axes,
43                                            color='blue',
44                                            zorder=zorder,
45                                            y=self.y,
46                                            x=self.x)
47        self.vertical_lines.qmax = self.qmax
48
49        self.horizontal_lines = HorizontalLines(self,
50                                                self.axes,
51                                                color='green',
52                                                zorder=zorder,
53                                                x=self.x,
54                                                y=self.y)
55        self.horizontal_lines.qmax = self.qmax
56        # draw the rectangle and plost the data 1D resulting
57        # of averaging data2D
58        self.update()
59        self._post_data()
60        self.setModelFromParams()
61
62    def update_and_post(self):
63        """
64        Update the slicer and plot the resulting data
65        """
66        self.update()
67        self._post_data()
68
69    def set_layer(self, n):
70        """
71        Allow adding plot to the same panel
72
73        :param n: the number of layer
74
75        """
76        self.layernum = n
77        self.update()
78
79    def clear(self):
80        """
81        Clear the slicer and all connected events related to this slicer
82        """
83        self.averager = None
84        self.clear_markers()
85        self.horizontal_lines.clear()
86        self.vertical_lines.clear()
87        self.base.connect.clearall()
88
89    def update(self):
90        """
91        Respond to changes in the model by recalculating the profiles and
92        resetting the widgets.
93        """
94        # #Update the slicer if an horizontal line is dragged
95        if self.horizontal_lines.has_move:
96            self.horizontal_lines.update()
97            self.vertical_lines.update(y=self.horizontal_lines.y)
98        # #Update the slicer if a vertical line is dragged
99        if self.vertical_lines.has_move:
100            self.vertical_lines.update()
101            self.horizontal_lines.update(x=self.vertical_lines.x)
102
103    def save(self, ev):
104        """
105        Remember the roughness for this layer and the next so that we
106        can restore on Esc.
107        """
108        self.vertical_lines.save(ev)
109        self.horizontal_lines.save(ev)
110
111    def _post_data(self):
112        pass
113
114    def post_data(self, new_slab=None, nbins=None, direction=None):
115        """
116        post data averaging in Qx or Qy given new_slab type
117
118        :param new_slab: slicer that determine with direction to average
119        :param nbins: the number of points plotted when averaging
120        :param direction: the direction of averaging
121
122        """
123        if self.direction is None:
124            self.direction = direction
125
126        x_min = -1 * numpy.fabs(self.vertical_lines.x)
127        x_max = numpy.fabs(self.vertical_lines.x)
128        y_min = -1 * numpy.fabs(self.horizontal_lines.y)
129        y_max = numpy.fabs(self.horizontal_lines.y)
130
131        if nbins is not None:
132            self.nbins = nbins
133        if self.averager is None:
134            if new_slab is None:
135                msg = "post data:cannot average , averager is empty"
136                raise ValueError(msg)
137            self.averager = new_slab
138        if self.direction == "X":
139            if self.fold:
140                x_low = 0
141            else:
142                x_low = numpy.fabs(x_min)
143            bin_width = (x_max + x_low) / self.nbins
144        elif self.direction == "Y":
145            if self.fold:
146                y_low = 0
147            else:
148                y_low = numpy.fabs(y_min)
149            bin_width = (y_max + y_low) / self.nbins
150        else:
151            msg = "post data:no Box Average direction was supplied"
152            raise ValueError(msg)
153        # # Average data2D given Qx or Qy
154        box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max,
155                            bin_width=bin_width)
156        box.fold = self.fold
157        boxavg = box(self.base.data)
158        # 3 Create Data1D to plot
159        if hasattr(boxavg, "dxl"):
160            dxl = boxavg.dxl
161        else:
162            dxl = None
163        if hasattr(boxavg, "dxw"):
164            dxw = boxavg.dxw
165        else:
166            dxw = None
167        new_plot = Data1D(x=boxavg.x, y=boxavg.y, dy=boxavg.dy)
168        new_plot.dxl = dxl
169        new_plot.dxw = dxw
170        new_plot.name = str(self.averager.__name__) + \
171                        "(" + self.base.data.name + ")"
172        new_plot.title = str(self.averager.__name__) + \
173                        "(" + self.base.data.name + ")"
174        new_plot.source = self.base.data.source
175        new_plot.interactive = True
176        new_plot.detector = self.base.data.detector
177        # # If the data file does not tell us what the axes are, just assume...
178        new_plot.xaxis("\\rm{Q}", "A^{-1}")
179        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
180
181        data = self.base.data
182        if hasattr(data, "scale") and data.scale == 'linear' and \
183                self.base.data.name.count("Residuals") > 0:
184            new_plot.ytransform = 'y'
185            new_plot.yaxis("\\rm{Residuals} ", "/")
186
187        #new_plot. = "2daverage" + self.base.data.name
188        new_plot.id = (self.averager.__name__) + self.base.data.name
189        new_plot.group_id = new_plot.id
190        new_plot.is_data = True
191        item = self._item
192        if self._item.parent() is not None:
193            item = self._item.parent()
194        GuiUtils.updateModelItemWithPlot(item, new_plot, new_plot.id)
195        self.base.manager.communicator.forcePlotDisplaySignal.emit([item, new_plot])
196
197        if self.update_model:
198            self.setModelFromParams()
199        self.draw()
200
201    def moveend(self, ev):
202        """
203        Called after a dragging event.
204        Post the slicer new parameters and creates a new Data1D
205        corresponding to the new average
206        """
207        self._post_data()
208
209    def restore(self):
210        """
211        Restore the roughness for this layer.
212        """
213        self.horizontal_lines.restore()
214        self.vertical_lines.restore()
215
216    def move(self, x, y, ev):
217        """
218        Process move to a new position, making sure that the move is allowed.
219        """
220        pass
221
222    def set_cursor(self, x, y):
223        pass
224
225    def getParams(self):
226        """
227        Store a copy of values of parameters of the slicer into a dictionary.
228
229        :return params: the dictionary created
230
231        """
232        params = {}
233        params["x_max"] = numpy.fabs(self.vertical_lines.x)
234        params["y_max"] = numpy.fabs(self.horizontal_lines.y)
235        params["nbins"] = self.nbins
236        return params
237
238    def setParams(self, params):
239        """
240        Receive a dictionary and reset the slicer with values contained
241        in the values of the dictionary.
242
243        :param params: a dictionary containing name of slicer parameters and
244            values the user assigned to the slicer.
245        """
246        self.x = float(numpy.fabs(params["x_max"]))
247        self.y = float(numpy.fabs(params["y_max"]))
248        self.nbins = params["nbins"]
249
250        self.horizontal_lines.update(x=self.x, y=self.y)
251        self.vertical_lines.update(x=self.x, y=self.y)
252        self.post_data(nbins=None)
253
254    def draw(self):
255        """
256        """
257        self.base.draw()
258
259
260class HorizontalLines(BaseInteractor):
261    """
262    Draw 2 Horizontal lines centered on (0,0) that can move
263    on the x- direction and in opposite direction
264    """
265    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
266        """
267        """
268        BaseInteractor.__init__(self, base, axes, color=color)
269        # Class initialization
270        self.markers = []
271        self.axes = axes
272        # Saving the end points of two lines
273        self.x = x
274        self.save_x = x
275
276        self.y = y
277        self.save_y = y
278        # Creating a marker
279        # Inner circle marker
280        self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
281                                           marker='s', markersize=10,
282                                           color=self.color, alpha=0.6,
283                                           pickradius=5, label="pick",
284                                           zorder=zorder,
285                                           visible=True)[0]
286        # Define 2 horizontal lines
287        self.top_line = self.axes.plot([self.x, -self.x], [self.y, self.y],
288                                       linestyle='-', marker='',
289                                       color=self.color, visible=True)[0]
290        self.bottom_line = self.axes.plot([self.x, -self.x], [-self.y, -self.y],
291                                          linestyle='-', marker='',
292                                          color=self.color, visible=True)[0]
293        # Flag to check the motion of the lines
294        self.has_move = False
295        # Connecting markers to mouse events and draw
296        self.connect_markers([self.top_line, self.inner_marker])
297        self.update()
298
299    def set_layer(self, n):
300        """
301        Allow adding plot to the same panel
302
303        :param n: the number of layer
304
305        """
306        self.layernum = n
307        self.update()
308
309    def clear(self):
310        """
311        Clear this slicer  and its markers
312        """
313        self.clear_markers()
314        self.inner_marker.remove()
315        self.top_line.remove()
316        self.bottom_line.remove()
317
318    def update(self, x=None, y=None):
319        """
320        Draw the new roughness on the graph.
321
322        :param x: x-coordinates to reset current class x
323        :param y: y-coordinates to reset current class y
324
325        """
326        # Reset x, y- coordinates if send as parameters
327        if x is not None:
328            self.x = numpy.sign(self.x) * numpy.fabs(x)
329        if y is not None:
330            self.y = numpy.sign(self.y) * numpy.fabs(y)
331        # Draw lines and markers
332        self.inner_marker.set(xdata=[0], ydata=[self.y])
333        self.top_line.set(xdata=[self.x, -self.x], ydata=[self.y, self.y])
334        self.bottom_line.set(xdata=[self.x, -self.x], ydata=[-self.y, -self.y])
335
336    def save(self, ev):
337        """
338        Remember the roughness for this layer and the next so that we
339        can restore on Esc.
340        """
341        self.save_x = self.x
342        self.save_y = self.y
343
344    def moveend(self, ev):
345        """
346        Called after a dragging this edge and set self.has_move to False
347        to specify the end of dragging motion
348        """
349        self.has_move = False
350        self.base.moveend(ev)
351
352    def restore(self):
353        """
354        Restore the roughness for this layer.
355        """
356        self.x = self.save_x
357        self.y = self.save_y
358
359    def move(self, x, y, ev):
360        """
361        Process move to a new position, making sure that the move is allowed.
362        """
363        self.y = y
364        self.has_move = True
365        self.base.base.update()
366
367
368class VerticalLines(BaseInteractor):
369    """
370    Select an annulus through a 2D plot
371    """
372    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
373        """
374        """
375        BaseInteractor.__init__(self, base, axes, color=color)
376        self.markers = []
377        self.axes = axes
378        self.x = numpy.fabs(x)
379        self.save_x = self.x
380        self.y = numpy.fabs(y)
381        self.save_y = y
382        # Inner circle marker
383        self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
384                                           marker='s', markersize=10,
385                                           color=self.color, alpha=0.6,
386                                           pickradius=5, label="pick",
387                                           zorder=zorder, visible=True)[0]
388        self.right_line = self.axes.plot([self.x, self.x],
389                                         [self.y, -self.y],
390                                         linestyle='-', marker='',
391                                         color=self.color, visible=True)[0]
392        self.left_line = self.axes.plot([-self.x, -self.x],
393                                        [self.y, -self.y],
394                                        linestyle='-', marker='',
395                                        color=self.color, visible=True)[0]
396        self.has_move = False
397        self.connect_markers([self.right_line, self.inner_marker])
398        self.update()
399
400    def set_layer(self, n):
401        """
402        Allow adding plot to the same panel
403
404        :param n: the number of layer
405
406        """
407        self.layernum = n
408        self.update()
409
410    def clear(self):
411        """
412        Clear this slicer  and its markers
413        """
414        self.clear_markers()
415        self.inner_marker.remove()
416        self.left_line.remove()
417        self.right_line.remove()
418
419    def update(self, x=None, y=None):
420        """
421        Draw the new roughness on the graph.
422
423        :param x: x-coordinates to reset current class x
424        :param y: y-coordinates to reset current class y
425
426        """
427        # Reset x, y -coordinates if given as parameters
428        if x is not None:
429            self.x = numpy.sign(self.x) * numpy.fabs(x)
430        if y is not None:
431            self.y = numpy.sign(self.y) * numpy.fabs(y)
432        # Draw lines and markers
433        self.inner_marker.set(xdata=[self.x], ydata=[0])
434        self.left_line.set(xdata=[-self.x, -self.x], ydata=[self.y, -self.y])
435        self.right_line.set(xdata=[self.x, self.x], ydata=[self.y, -self.y])
436
437    def save(self, ev):
438        """
439        Remember the roughness for this layer and the next so that we
440        can restore on Esc.
441        """
442        self.save_x = self.x
443        self.save_y = self.y
444
445    def moveend(self, ev):
446        """
447        Called after a dragging this edge and set self.has_move to False
448        to specify the end of dragging motion
449        """
450        self.has_move = False
451        self.base.moveend(ev)
452
453    def restore(self):
454        """
455        Restore the roughness for this layer.
456        """
457        self.x = self.save_x
458        self.y = self.save_y
459
460    def move(self, x, y, ev):
461        """
462        Process move to a new position, making sure that the move is allowed.
463        """
464        self.has_move = True
465        self.x = x
466        self.base.base.update()
467
468
469class BoxInteractorX(BoxInteractor):
470    """
471    Average in Qx direction
472    """
473    def __init__(self, base, axes, item=None, color='black', zorder=3):
474        BoxInteractor.__init__(self, base, axes, item=item, color=color)
475        self.base = base
476        self._post_data()
477
478    def _post_data(self):
479        """
480        Post data creating by averaging in Qx direction
481        """
482        from sas.sascalc.dataloader.manipulations import SlabX
483        self.post_data(SlabX, direction="X")
484
485
486class BoxInteractorY(BoxInteractor):
487    """
488    Average in Qy direction
489    """
490    def __init__(self, base, axes, item=None, color='black', zorder=3):
491        BoxInteractor.__init__(self, base, axes, item=item, color=color)
492        self.base = base
493        self._post_data()
494
495    def _post_data(self):
496        """
497        Post data creating by averaging in Qy direction
498        """
499        from sas.sascalc.dataloader.manipulations import SlabY
500        self.post_data(SlabY, direction="Y")
501
Note: See TracBrowser for help on using the repository browser.