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

Last change on this file since 5a2bb75 was e20870bc, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Masking dialog for fitting

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