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

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

Automatically show sector/annulus/box plots SASVIEW-980

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