source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py @ 965fbd8

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 965fbd8 was 3bdbfcc, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Reimplementation of the slicer functionality

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