source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py @ 18f11a6

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 18f11a6 was fa81e94, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial commit of the P(r) inversion perspective.
Code merged from Jeff Krzywon's ESS_GUI_Pr branch.
Also, minor 2to3 mods to sascalc/sasgui to enble error free setup.

  • 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.Utilities.GuiUtils as GuiUtils
8from sas.qtgui.Plotting.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        GuiUtils.updateModelItemWithPlot(self._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.