# source:sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/boxSum.py@3bdbfcc

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

Reimplementation of the slicer functionality

• Property mode set to `100644`
File size: 26.6 KB
Line
1"""
2Boxsum Class: determine 2 rectangular area to compute
3the sum of pixel of a Data.
4"""
5import numpy
6from PyQt4 import QtGui
7from PyQt4 import QtCore
8from sas.qtgui.GuiUtils import formatNumber
9
10from BaseInteractor import _BaseInteractor
13
14from sas.qtgui.SlicerModel import SlicerModel
15
16
17class BoxSumCalculator(_BaseInteractor):
18    """
19    Boxsum Class: determine 2 rectangular area to compute
20    the sum of pixel of a Data.
21    Uses PointerInteractor , VerticalDoubleLine,HorizontalDoubleLine.
22    @param zorder:  Artists with lower zorder values are drawn first.
23    @param x_min: the minimum value of the x coordinate
24    @param x_max: the maximum value of the x coordinate
25    @param y_min: the minimum value of the y coordinate
26    @param y_max: the maximum value of the y coordinate
27
28    """
29    def __init__(self, base, axes, color='black', zorder=3):
30        _BaseInteractor.__init__(self, base, axes, color=color)
31
32        # list of Boxsmun markers
33        self.markers = []
34        self.axes = axes
35        self._model = None
36        self.update_model = False
37
38        # connect the artist for the motion
39        self.connect = self.base.connect
40
41        # when qmax is reached the selected line is reset the its previous value
42        self.qmax = min(self.base.data.xmax, self.base.data.xmin)
43
44        # Define the boxsum limits
45        self.xmin = -1 * 0.5 * min(numpy.fabs(self.base.data.xmax),
46                                   numpy.fabs(self.base.data.xmin))
47        self.ymin = -1 * 0.5 * min(numpy.fabs(self.base.data.xmax),
48                                   numpy.fabs(self.base.data.xmin))
49        self.xmax = 0.5 * min(numpy.fabs(self.base.data.xmax),
50                              numpy.fabs(self.base.data.xmin))
51        self.ymax = 0.5 * min(numpy.fabs(self.base.data.xmax),
52                              numpy.fabs(self.base.data.xmin))
53        # center of the boxSum
54        self.center_x = 0.0002
55        self.center_y = 0.0003
56        # Number of points on the plot
57        self.nbins = 20
58        # Define initial result the summation
59        self.count = 0
60        self.error = 0
61        self.total = 0
62        self.totalerror = 0
63        self.points = 0
64        # Flag to determine if the current figure has moved
65        # set to False == no motion , set to True== motion
66        self.has_move = False
67        # Create Boxsum edges
68        self.horizontal_lines = HorizontalDoubleLine(self,
69                                                     self.axes,
70                                                     color='blue',
71                                                     zorder=zorder,
72                                                     y=self.ymax,
73                                                     x=self.xmax,
74                                                     center_x=self.center_x,
75                                                     center_y=self.center_y)
76        self.horizontal_lines.qmax = self.qmax
77
78        self.vertical_lines = VerticalDoubleLine(self,
79                                                 self.axes,
80                                                 color='black',
81                                                 zorder=zorder,
82                                                 y=self.ymax,
83                                                 x=self.xmax,
84                                                 center_x=self.center_x,
85                                                 center_y=self.center_y)
86        self.vertical_lines.qmax = self.qmax
87
88        self.center = PointInteractor(self,
89                                      self.axes, color='grey',
90                                      zorder=zorder,
91                                      center_x=self.center_x,
92                                      center_y=self.center_y)
93        # Save the name of the slicer panel associate with this slicer
94        self.panel_name = ""
95        # Update and post slicer parameters
96        self.update_model = False
97        self.update()
98        self.postData()
99
100        # set up the model
101        self._model = QtGui.QStandardItemModel(1, 9)
102        self.setModelFromParams()
103        self.update_model = True
104        self._model.itemChanged.connect(self.setParamsFromModel)
105
106    def setModelFromParams(self):
107        """
108        Set up the Qt model for data handling between controls
109        """
110        parameters = self.getParams()
111        # Crete/overwrite model items
112        self._model.setData(self._model.index(0, 0),
113                    QtCore.QVariant(formatNumber(parameters['Height'])))
114        self._model.setData(self._model.index(0, 1),
115                    QtCore.QVariant(formatNumber(parameters['Width'])))
116        self._model.setData(self._model.index(0, 2),
117                    QtCore.QVariant(formatNumber(parameters['center_x'])))
118        self._model.setData(self._model.index(0, 3),
119                    QtCore.QVariant(formatNumber(parameters['center_y'])))
120
122
123    def model(self):
124        ''' model accessor '''
125        return self._model
126
128        """
129        Cast model content onto "read-only" subset of parameters
130        """
131        parameters = self.getParams()
132        self._model.setData(self._model.index(0, 4),
133                    QtCore.QVariant(formatNumber(parameters['avg'])))
134        self._model.setData(self._model.index(0, 5),
135                    QtCore.QVariant(formatNumber(parameters['avg_error'])))
136        self._model.setData(self._model.index(0, 6),
137                    QtCore.QVariant(formatNumber(parameters['sum'])))
138        self._model.setData(self._model.index(0, 7),
139                    QtCore.QVariant(formatNumber(parameters['sum_error'])))
140        self._model.setData(self._model.index(0, 8),
141                    QtCore.QVariant(formatNumber(parameters['num_points'])))
142
143    def setParamsFromModel(self):
144        """
145        Cast model content onto params dict
146        """
147        params = {}
148        params["Height"] = float(self.model().item(0, 0).text())
149        params["Width"] = float(self.model().item(0, 1).text())
150        params["center_x"] = float(self.model().item(0, 2).text())
151        params["center_y"] = float(self.model().item(0, 3).text())
152        self.update_model = False
153        self.setParams(params)
155        self.update_model = True
156
157    def setPanelName(self, name):
158        """
159        Store the name of the panel associated to this slicer
160        @param name: the name of this panel
161        """
162        self.panel_name = name
163
164    def setLayer(self, n):
165        """
166        Allow adding plot to the same panel
167        :param n: the number of layer
168        """
169        self.layernum = n
170        self.update()
171
172    def clear(self):
173        """
174        Clear the slicer and all connected events related to this slicer
175        """
176        self.clear_markers()
177        self.horizontal_lines.clear()
178        self.vertical_lines.clear()
179        self.center.clear()
180        self.base.connect.clearall()
181
182    def update(self):
183        """
184        Respond to changes in the model by recalculating the profiles and
185        resetting the widgets.
186        """
187        # check if the center point has moved and update the figure accordingly
188        if self.center.has_move:
189            self.center.update()
190            self.horizontal_lines.update(center=self.center)
191            self.vertical_lines.update(center=self.center)
192        # check if the horizontal lines have moved and
193        # update the figure accordingly
194        if self.horizontal_lines.has_move:
195            self.horizontal_lines.update()
196            self.vertical_lines.update(y1=self.horizontal_lines.y1,
197                                       y2=self.horizontal_lines.y2,
198                                       height=self.horizontal_lines.half_height)
199        # check if the vertical lines have moved and
200        # update the figure accordingly
201        if self.vertical_lines.has_move:
202            self.vertical_lines.update()
203            self.horizontal_lines.update(x1=self.vertical_lines.x1,
204                                         x2=self.vertical_lines.x2,
205                                         width=self.vertical_lines.half_width)
206
207    def save(self, ev):
208        """
209        Remember the roughness for this layer and the next so that we
210        can restore on Esc.
211        """
212        self.horizontal_lines.save(ev)
213        self.vertical_lines.save(ev)
214        self.center.save(ev)
215
216    def postData(self):
217        """
218        Get the limits of the boxsum and compute the sum of the pixel
219        contained in that region and the error on that sum
220        """
221        # the region of the summation
222        x_min = self.horizontal_lines.x2
223        x_max = self.horizontal_lines.x1
224        y_min = self.vertical_lines.y2
225        y_max = self.vertical_lines.y1
226        #computation of the sum and its error
227        box = Boxavg(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
228        self.count, self.error = box(self.base.data)
229        # Dig out number of points summed, SMK & PDB, 04/03/2013
230        boxtotal = Boxsum(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
231        self.total, self.totalerror, self.points = boxtotal(self.base.data)
232        if self.update_model:
233            self.setModelFromParams()
234        self.draw()
235
236    def moveend(self, ev):
237        """
238        After a dragging motion this function is called to compute
239        the error and the sum of pixel of a given data 2D
240        """
241        # compute error an d sum of data's pixel
242        self.postData()
243
244    def restore(self):
245        """
246        Restore the roughness for this layer.
247        """
248        self.horizontal_lines.restore()
249        self.vertical_lines.restore()
250        self.center.restore()
251
252    def getParams(self):
253        """
254        Store a copy of values of parameters of the slicer into a dictionary.
255        :return params: the dictionary created
256        """
257        params = {}
258        params["Width"] = numpy.fabs(self.vertical_lines.half_width) * 2
259        params["Height"] = numpy.fabs(self.horizontal_lines.half_height) * 2
260        params["center_x"] = self.center.x
261        params["center_y"] = self.center.y
262        params["num_points"] = self.points
263        params["avg"] = self.count
264        params["avg_error"] = self.error
265        params["sum"] = self.total
266        params["sum_error"] = self.totalerror
267        return params
268
269    def getResult(self):
270        """
271        Return the result of box summation
272        """
273        result = {}
274        result["num_points"] = self.points
275        result["avg"] = self.count
276        result["avg_error"] = self.error
277        result["sum"] = self.total
278        result["sum_error"] = self.totalerror
279        return result
280
281    def setParams(self, params):
282        """
283        Receive a dictionary and reset the slicer with values contained
284        in the values of the dictionary.
285        :param params: a dictionary containing name of slicer parameters
286        and values the user assigned to the slicer.
287        """
288        x_max = numpy.fabs(params["Width"]) / 2
289        y_max = numpy.fabs(params["Height"]) / 2
290
291        self.center_x = params["center_x"]
292        self.center_y = params["center_y"]
293        # update the slicer given values of params
294        self.center.update(center_x=self.center_x, center_y=self.center_y)
295        self.horizontal_lines.update(center=self.center,
296                                     width=x_max, height=y_max)
297        self.vertical_lines.update(center=self.center,
298                                   width=x_max, height=y_max)
299        # compute the new error and sum given values of params
300        self.postData()
301
302    def draw(self):
303        """ Redraw canvas"""
304        self.base.draw()
305
306
307
308class PointInteractor(_BaseInteractor):
309    """
310    Draw a point that can be dragged with the marker.
311    this class controls the motion the center of the BoxSum
312    """
313    def __init__(self, base, axes, color='black', zorder=5, center_x=0.0,
314                 center_y=0.0):
315        _BaseInteractor.__init__(self, base, axes, color=color)
316        # Initialization the class
317        self.markers = []
318        self.axes = axes
319        # center coordinates
320        self.x = center_x
321        self.y = center_y
322        # saved value of the center coordinates
323        self.save_x = center_x
324        self.save_y = center_y
325        # Create a marker
326        self.center_marker = self.axes.plot([self.x], [self.y], linestyle='',
327                                            marker='s', markersize=10,
328                                            color=self.color, alpha=0.6,
330                                            zorder=zorder,
331                                            visible=True)[0]
332        # Draw a point
333        self.center = self.axes.plot([self.x], [self.y],
334                                     linestyle='-', marker='',
335                                     color=self.color,
336                                     visible=True)[0]
337        # Flag to determine the motion this point
338        self.has_move = False
339        # connecting the marker to allow them to move
340        self.connect_markers([self.center_marker])
341        # Update the figure
342        self.update()
343
344    def setLayer(self, n):
345        """
346        Allow adding plot to the same panel
347        @param n: the number of layer
348        """
349        self.layernum = n
350        self.update()
351
352    def clear(self):
353        """
354        Clear this figure and its markers
355        """
356        self.clear_markers()
357        self.center.remove()
358        self.center_marker.remove()
359
360    def update(self, center_x=None, center_y=None):
361        """
362        Draw the new roughness on the graph.
363        """
364        if center_x != None:
365            self.x = center_x
366        if center_y != None:
367            self.y = center_y
368        self.center_marker.set(xdata=[self.x], ydata=[self.y])
369        self.center.set(xdata=[self.x], ydata=[self.y])
370
371    def save(self, ev):
372        """
373        Remember the roughness for this layer and the next so that we
374        can restore on Esc.
375        """
376        self.save_x = self.x
377        self.save_y = self.y
378
379    def moveend(self, ev):
380        """
381        """
382        self.has_move = False
383        self.base.moveend(ev)
384
385    def restore(self):
386        """
387        Restore the roughness for this layer.
388        """
389        self.y = self.save_y
390        self.x = self.save_x
391
392    def move(self, x, y, ev):
393        """
394        Process move to a new position, making sure that the move is allowed.
395        """
396        self.x = x
397        self.y = y
398        self.has_move = True
399        self.base.base.update()
400
401    def setCursor(self, x, y):
402        """
403        """
404        self.move(x, y, None)
405        self.update()
406
407class VerticalDoubleLine(_BaseInteractor):
408    """
409    Draw 2 vertical lines moving in opposite direction and centered on
410    a point (PointInteractor)
411    """
412    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5,
413                 center_x=0.0, center_y=0.0):
414        _BaseInteractor.__init__(self, base, axes, color=color)
415        # Initialization the class
416        self.markers = []
417        self.axes = axes
418        # Center coordinates
419        self.center_x = center_x
420        self.center_y = center_y
421        # defined end points vertical lignes and their saved values
422        self.y1 = y + self.center_y
423        self.save_y1 = self.y1
424
425        delta = self.y1 - self.center_y
426        self.y2 = self.center_y - delta
427        self.save_y2 = self.y2
428
429        self.x1 = x + self.center_x
430        self.save_x1 = self.x1
431
432        delta = self.x1 - self.center_x
433        self.x2 = self.center_x - delta
434        self.save_x2 = self.x2
435        # # save the color of the line
436        self.color = color
437        # the height of the rectangle
438        self.half_height = numpy.fabs(y)
439        self.save_half_height = numpy.fabs(y)
440        # the with of the rectangle
441        self.half_width = numpy.fabs(self.x1 - self.x2) / 2
442        self.save_half_width = numpy.fabs(self.x1 - self.x2) / 2
443        # Create marker
444        self.right_marker = self.axes.plot([self.x1], [0], linestyle='',
445                                           marker='s', markersize=10,
446                                           color=self.color, alpha=0.6,
448                                           zorder=zorder, visible=True)[0]
449
450        # Define the left and right lines of the rectangle
451        self.right_line = self.axes.plot([self.x1, self.x1], [self.y1, self.y2],
452                                         linestyle='-', marker='',
453                                         color=self.color, visible=True)[0]
454        self.left_line = self.axes.plot([self.x2, self.x2], [self.y1, self.y2],
455                                        linestyle='-', marker='',
456                                        color=self.color, visible=True)[0]
457        # Flag to determine if the lines have moved
458        self.has_move = False
459        # Connection the marker and draw the pictures
460        self.connect_markers([self.right_marker])
461        self.update()
462
463    def setLayer(self, n):
464        """
465        Allow adding plot to the same panel
466        :param n: the number of layer
467        """
468        self.layernum = n
469        self.update()
470
471    def clear(self):
472        """
473        Clear this slicer  and its markers
474        """
475        self.clear_markers()
476        self.right_marker.remove()
477        self.right_line.remove()
478        self.left_line.remove()
479
480    def update(self, x1=None, x2=None, y1=None, y2=None, width=None,
481               height=None, center=None):
482        """
483        Draw the new roughness on the graph.
484        :param x1: new maximum value of x coordinates
485        :param x2: new minimum value of x coordinates
486        :param y1: new maximum value of y coordinates
487        :param y2: new minimum value of y coordinates
488        :param width: is the width of the new rectangle
489        :param height: is the height of the new rectangle
490        :param center: provided x, y  coordinates of the center point
491        """
492        # Save the new height, witdh of the rectangle if given as a param
493        if width is not None:
494            self.half_width = width
495        if height is not None:
496            self.half_height = height
497        # If new  center coordinates are given draw the rectangle
498        # given these value
499        if center is not None:
500            self.center_x = center.x
501            self.center_y = center.y
502            self.x1 = self.half_width + self.center_x
503            self.x2 = -self.half_width + self.center_x
504            self.y1 = self.half_height + self.center_y
505            self.y2 = -self.half_height + self.center_y
506
507            self.right_marker.set(xdata=[self.x1], ydata=[self.center_y])
508            self.right_line.set(xdata=[self.x1, self.x1],
509                                ydata=[self.y1, self.y2])
510            self.left_line.set(xdata=[self.x2, self.x2],
511                               ydata=[self.y1, self.y2])
512            return
513        # if x1, y1, y2, y3 are given draw the rectangle with this value
514        if x1 is not None:
515            self.x1 = x1
516        if x2 is not None:
517            self.x2 = x2
518        if y1 is not None:
519            self.y1 = y1
520        if y2 is not None:
521            self.y2 = y2
522        # Draw 2 vertical lines and a marker
523        self.right_marker.set(xdata=[self.x1], ydata=[self.center_y])
524        self.right_line.set(xdata=[self.x1, self.x1], ydata=[self.y1, self.y2])
525        self.left_line.set(xdata=[self.x2, self.x2], ydata=[self.y1, self.y2])
526
527    def save(self, ev):
528        """
529        Remember the roughness for this layer and the next so that we
530        can restore on Esc.
531        """
532        self.save_x2 = self.x2
533        self.save_y2 = self.y2
534        self.save_x1 = self.x1
535        self.save_y1 = self.y1
536        self.save_half_height = self.half_height
537        self.save_half_width = self.half_width
538
539    def moveend(self, ev):
540        """
541        After a dragging motion reset the flag self.has_move to False
542        """
543        self.has_move = False
544        self.base.moveend(ev)
545
546    def restore(self):
547        """
548        Restore the roughness for this layer.
549        """
550        self.y2 = self.save_y2
551        self.x2 = self.save_x2
552        self.y1 = self.save_y1
553        self.x1 = self.save_x1
554        self.half_height = self.save_half_height
555        self.half_width = self.save_half_width
556
557    def move(self, x, y, ev):
558        """
559        Process move to a new position, making sure that the move is allowed.
560        """
561        self.x1 = x
562        delta = self.x1 - self.center_x
563        self.x2 = self.center_x - delta
564        self.half_width = numpy.fabs(self.x1 - self.x2) / 2
565        self.has_move = True
566        self.base.base.update()
567
568    def setCursor(self, x, y):
569        """
570        Update the figure given x and y
571        """
572        self.move(x, y, None)
573        self.update()
574
575class HorizontalDoubleLine(_BaseInteractor):
576    """
577    Select an annulus through a 2D plot
578    """
579    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5,
580                 center_x=0.0, center_y=0.0):
581
582        _BaseInteractor.__init__(self, base, axes, color=color)
583        # Initialization the class
584        self.markers = []
585        self.axes = axes
586        # Center coordinates
587        self.center_x = center_x
588        self.center_y = center_y
589        self.y1 = y + self.center_y
590        self.save_y1 = self.y1
591        delta = self.y1 - self.center_y
592        self.y2 = self.center_y - delta
593        self.save_y2 = self.y2
594        self.x1 = x + self.center_x
595        self.save_x1 = self.x1
596        delta = self.x1 - self.center_x
597        self.x2 = self.center_x - delta
598        self.save_x2 = self.x2
599        self.color = color
600        self.half_height = numpy.fabs(y)
601        self.save_half_height = numpy.fabs(y)
602        self.half_width = numpy.fabs(x)
603        self.save_half_width = numpy.fabs(x)
604        self.top_marker = self.axes.plot([0], [self.y1], linestyle='',
605                                         marker='s', markersize=10,
606                                         color=self.color, alpha=0.6,
608                                         zorder=zorder, visible=True)[0]
609
610        # Define 2 horizotnal lines
611        self.top_line = self.axes.plot([self.x1, -self.x1], [self.y1, self.y1],
612                                       linestyle='-', marker='',
613                                       color=self.color, visible=True)[0]
614        self.bottom_line = self.axes.plot([self.x1, -self.x1],
615                                          [self.y2, self.y2],
616                                          linestyle='-', marker='',
617                                          color=self.color, visible=True)[0]
618        # Flag to determine if the lines have moved
619        self.has_move = False
620        # connection the marker and draw the pictures
621        self.connect_markers([self.top_marker])
622        self.update()
623
624    def setLayer(self, n):
625        """
626        Allow adding plot to the same panel
627        @param n: the number of layer
628        """
629        self.layernum = n
630        self.update()
631
632    def clear(self):
633        """
634        Clear this figure and its markers
635        """
636        self.clear_markers()
637        self.top_marker.remove()
638        self.bottom_line.remove()
639        self.top_line.remove()
640
641    def update(self, x1=None, x2=None, y1=None, y2=None,
642               width=None, height=None, center=None):
643        """
644        Draw the new roughness on the graph.
645        :param x1: new maximum value of x coordinates
646        :param x2: new minimum value of x coordinates
647        :param y1: new maximum value of y coordinates
648        :param y2: new minimum value of y coordinates
649        :param width: is the width of the new rectangle
650        :param height: is the height of the new rectangle
651        :param center: provided x, y  coordinates of the center point
652        """
653        # Save the new height, witdh of the rectangle if given as a param
654        if width is not None:
655            self.half_width = width
656        if height is not None:
657            self.half_height = height
658        # If new  center coordinates are given draw the rectangle
659        # given these value
660        if center is not None:
661            self.center_x = center.x
662            self.center_y = center.y
663            self.x1 = self.half_width + self.center_x
664            self.x2 = -self.half_width + self.center_x
665
666            self.y1 = self.half_height + self.center_y
667            self.y2 = -self.half_height + self.center_y
668
669            self.top_marker.set(xdata=[self.center_x], ydata=[self.y1])
670            self.top_line.set(xdata=[self.x1, self.x2],
671                              ydata=[self.y1, self.y1])
672            self.bottom_line.set(xdata=[self.x1, self.x2],
673                                 ydata=[self.y2, self.y2])
674            return
675        # if x1, y1, y2, y3 are given draw the rectangle with this value
676        if x1 is not None:
677            self.x1 = x1
678        if x2 is not None:
679            self.x2 = x2
680        if y1 is not None:
681            self.y1 = y1
682        if y2 is not None:
683            self.y2 = y2
684        # Draw 2 vertical lines and a marker
685        self.top_marker.set(xdata=[self.center_x], ydata=[self.y1])
686        self.top_line.set(xdata=[self.x1, self.x2], ydata=[self.y1, self.y1])
687        self.bottom_line.set(xdata=[self.x1, self.x2], ydata=[self.y2, self.y2])
688
689    def save(self, ev):
690        """
691        Remember the roughness for this layer and the next so that we
692        can restore on Esc.
693        """
694        self.save_x2 = self.x2
695        self.save_y2 = self.y2
696        self.save_x1 = self.x1
697        self.save_y1 = self.y1
698        self.save_half_height = self.half_height
699        self.save_half_width = self.half_width
700
701    def moveend(self, ev):
702        """
703        After a dragging motion reset the flag self.has_move to False
704        """
705        self.has_move = False
706        self.base.moveend(ev)
707
708    def restore(self):
709        """
710        Restore the roughness for this layer.
711        """
712        self.y2 = self.save_y2
713        self.x2 = self.save_x2
714        self.y1 = self.save_y1
715        self.x1 = self.save_x1
716        self.half_height = self.save_half_height
717        self.half_width = self.save_half_width
718
719    def move(self, x, y, ev):
720        """
721        Process move to a new position, making sure that the move is allowed.
722        """
723        self.y1 = y
724        delta = self.y1 - self.center_y
725        self.y2 = self.center_y - delta
726        self.half_height = numpy.fabs(self.y1) - self.center_y
727        self.has_move = True
728        self.base.base.update()
729
730    def setCursor(self, x, y):
731        """
732        Update the figure given x and y
733        """
734        self.move(x, y, None)
735        self.update()
Note: See TracBrowser for help on using the repository browser.