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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 63467b6 was 63467b6, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Improved handling of 2d plot children. Refactored model tree search.

  • Property mode set to 100644
File size: 16.4 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.group_id = "2daverage" + self.base.data.name
188        new_plot.id = (self.averager.__name__) + self.base.data.name
189        new_plot.is_data = True
190        if self._item.parent() is not None:
191            item = self._item.parent()
192        GuiUtils.updateModelItemWithPlot(item, new_plot, new_plot.id)
193
194        if self.update_model:
195            self.setModelFromParams()
196        self.draw()
197
198    def moveend(self, ev):
199        """
200        Called after a dragging event.
201        Post the slicer new parameters and creates a new Data1D
202        corresponding to the new average
203        """
204        self._post_data()
205
206    def restore(self):
207        """
208        Restore the roughness for this layer.
209        """
210        self.horizontal_lines.restore()
211        self.vertical_lines.restore()
212
213    def move(self, x, y, ev):
214        """
215        Process move to a new position, making sure that the move is allowed.
216        """
217        pass
218
219    def set_cursor(self, x, y):
220        pass
221
222    def getParams(self):
223        """
224        Store a copy of values of parameters of the slicer into a dictionary.
225
226        :return params: the dictionary created
227
228        """
229        params = {}
230        params["x_max"] = numpy.fabs(self.vertical_lines.x)
231        params["y_max"] = numpy.fabs(self.horizontal_lines.y)
232        params["nbins"] = self.nbins
233        return params
234
235    def setParams(self, params):
236        """
237        Receive a dictionary and reset the slicer with values contained
238        in the values of the dictionary.
239
240        :param params: a dictionary containing name of slicer parameters and
241            values the user assigned to the slicer.
242        """
243        self.x = float(numpy.fabs(params["x_max"]))
244        self.y = float(numpy.fabs(params["y_max"]))
245        self.nbins = params["nbins"]
246
247        self.horizontal_lines.update(x=self.x, y=self.y)
248        self.vertical_lines.update(x=self.x, y=self.y)
249        self.post_data(nbins=None)
250
251    def draw(self):
252        """
253        """
254        self.base.draw()
255
256
257class HorizontalLines(BaseInteractor):
258    """
259    Draw 2 Horizontal lines centered on (0,0) that can move
260    on the x- direction and in opposite direction
261    """
262    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
263        """
264        """
265        BaseInteractor.__init__(self, base, axes, color=color)
266        # Class initialization
267        self.markers = []
268        self.axes = axes
269        # Saving the end points of two lines
270        self.x = x
271        self.save_x = x
272
273        self.y = y
274        self.save_y = y
275        # Creating a marker
276        # Inner circle marker
277        self.inner_marker = self.axes.plot([0], [self.y], linestyle='',
278                                           marker='s', markersize=10,
279                                           color=self.color, alpha=0.6,
280                                           pickradius=5, label="pick",
281                                           zorder=zorder,
282                                           visible=True)[0]
283        # Define 2 horizontal lines
284        self.top_line = self.axes.plot([self.x, -self.x], [self.y, self.y],
285                                       linestyle='-', marker='',
286                                       color=self.color, visible=True)[0]
287        self.bottom_line = self.axes.plot([self.x, -self.x], [-self.y, -self.y],
288                                          linestyle='-', marker='',
289                                          color=self.color, visible=True)[0]
290        # Flag to check the motion of the lines
291        self.has_move = False
292        # Connecting markers to mouse events and draw
293        self.connect_markers([self.top_line, self.inner_marker])
294        self.update()
295
296    def set_layer(self, n):
297        """
298        Allow adding plot to the same panel
299
300        :param n: the number of layer
301
302        """
303        self.layernum = n
304        self.update()
305
306    def clear(self):
307        """
308        Clear this slicer  and its markers
309        """
310        self.clear_markers()
311        self.inner_marker.remove()
312        self.top_line.remove()
313        self.bottom_line.remove()
314
315    def update(self, x=None, y=None):
316        """
317        Draw the new roughness on the graph.
318
319        :param x: x-coordinates to reset current class x
320        :param y: y-coordinates to reset current class y
321
322        """
323        # Reset x, y- coordinates if send as parameters
324        if x is not None:
325            self.x = numpy.sign(self.x) * numpy.fabs(x)
326        if y is not None:
327            self.y = numpy.sign(self.y) * numpy.fabs(y)
328        # Draw lines and markers
329        self.inner_marker.set(xdata=[0], ydata=[self.y])
330        self.top_line.set(xdata=[self.x, -self.x], ydata=[self.y, self.y])
331        self.bottom_line.set(xdata=[self.x, -self.x], ydata=[-self.y, -self.y])
332
333    def save(self, ev):
334        """
335        Remember the roughness for this layer and the next so that we
336        can restore on Esc.
337        """
338        self.save_x = self.x
339        self.save_y = self.y
340
341    def moveend(self, ev):
342        """
343        Called after a dragging this edge and set self.has_move to False
344        to specify the end of dragging motion
345        """
346        self.has_move = False
347        self.base.moveend(ev)
348
349    def restore(self):
350        """
351        Restore the roughness for this layer.
352        """
353        self.x = self.save_x
354        self.y = self.save_y
355
356    def move(self, x, y, ev):
357        """
358        Process move to a new position, making sure that the move is allowed.
359        """
360        self.y = y
361        self.has_move = True
362        self.base.base.update()
363
364
365class VerticalLines(BaseInteractor):
366    """
367    Select an annulus through a 2D plot
368    """
369    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5):
370        """
371        """
372        BaseInteractor.__init__(self, base, axes, color=color)
373        self.markers = []
374        self.axes = axes
375        self.x = numpy.fabs(x)
376        self.save_x = self.x
377        self.y = numpy.fabs(y)
378        self.save_y = y
379        # Inner circle marker
380        self.inner_marker = self.axes.plot([self.x], [0], linestyle='',
381                                           marker='s', markersize=10,
382                                           color=self.color, alpha=0.6,
383                                           pickradius=5, label="pick",
384                                           zorder=zorder, visible=True)[0]
385        self.right_line = self.axes.plot([self.x, self.x],
386                                         [self.y, -self.y],
387                                         linestyle='-', marker='',
388                                         color=self.color, visible=True)[0]
389        self.left_line = self.axes.plot([-self.x, -self.x],
390                                        [self.y, -self.y],
391                                        linestyle='-', marker='',
392                                        color=self.color, visible=True)[0]
393        self.has_move = False
394        self.connect_markers([self.right_line, self.inner_marker])
395        self.update()
396
397    def set_layer(self, n):
398        """
399        Allow adding plot to the same panel
400
401        :param n: the number of layer
402
403        """
404        self.layernum = n
405        self.update()
406
407    def clear(self):
408        """
409        Clear this slicer  and its markers
410        """
411        self.clear_markers()
412        self.inner_marker.remove()
413        self.left_line.remove()
414        self.right_line.remove()
415
416    def update(self, x=None, y=None):
417        """
418        Draw the new roughness on the graph.
419
420        :param x: x-coordinates to reset current class x
421        :param y: y-coordinates to reset current class y
422
423        """
424        # Reset x, y -coordinates if given as parameters
425        if x is not None:
426            self.x = numpy.sign(self.x) * numpy.fabs(x)
427        if y is not None:
428            self.y = numpy.sign(self.y) * numpy.fabs(y)
429        # Draw lines and markers
430        self.inner_marker.set(xdata=[self.x], ydata=[0])
431        self.left_line.set(xdata=[-self.x, -self.x], ydata=[self.y, -self.y])
432        self.right_line.set(xdata=[self.x, self.x], ydata=[self.y, -self.y])
433
434    def save(self, ev):
435        """
436        Remember the roughness for this layer and the next so that we
437        can restore on Esc.
438        """
439        self.save_x = self.x
440        self.save_y = self.y
441
442    def moveend(self, ev):
443        """
444        Called after a dragging this edge and set self.has_move to False
445        to specify the end of dragging motion
446        """
447        self.has_move = False
448        self.base.moveend(ev)
449
450    def restore(self):
451        """
452        Restore the roughness for this layer.
453        """
454        self.x = self.save_x
455        self.y = self.save_y
456
457    def move(self, x, y, ev):
458        """
459        Process move to a new position, making sure that the move is allowed.
460        """
461        self.has_move = True
462        self.x = x
463        self.base.base.update()
464
465
466class BoxInteractorX(BoxInteractor):
467    """
468    Average in Qx direction
469    """
470    def __init__(self, base, axes, item=None, color='black', zorder=3):
471        BoxInteractor.__init__(self, base, axes, item=item, color=color)
472        self.base = base
473        self._post_data()
474
475    def _post_data(self):
476        """
477        Post data creating by averaging in Qx direction
478        """
479        from sas.sascalc.dataloader.manipulations import SlabX
480        self.post_data(SlabX, direction="X")
481
482
483class BoxInteractorY(BoxInteractor):
484    """
485    Average in Qy direction
486    """
487    def __init__(self, base, axes, item=None, color='black', zorder=3):
488        BoxInteractor.__init__(self, base, axes, item=item, color=color)
489        self.base = base
490        self._post_data()
491
492    def _post_data(self):
493        """
494        Post data creating by averaging in Qy direction
495        """
496        from sas.sascalc.dataloader.manipulations import SlabY
497        self.post_data(SlabY, direction="Y")
498
Note: See TracBrowser for help on using the repository browser.