source: sasview/src/sas/qtgui/Plotter2D.py @ 49e124c

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

Initial functionality of Plot2D

  • Property mode set to 100755
File size: 11.5 KB
Line 
1import logging
2import copy
3import numpy
4import pylab
5
6from PyQt4 import QtGui
7
8# TODO: Replace the qt4agg calls below with qt5 equivalent.
9# Requires some code modifications.
10# https://www.boxcontrol.net/embedding-matplotlib-plot-on-pyqt5-gui.html
11#
12from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
13from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
14import matplotlib.pyplot as plt
15
16DEFAULT_CMAP = pylab.cm.jet
17
18import PlotHelper
19
20class Plotter2D(QtGui.QDialog):
21    def __init__(self, parent=None):
22        super(Plotter2D, self).__init__(parent)
23
24        # Required for the communicator
25        self.parent = parent
26
27        # a figure instance to plot on
28        self.figure = plt.figure()
29
30        # this is the Canvas Widget that displays the `figure`
31        # it takes the `figure` instance as a parameter to __init__
32        self.canvas = FigureCanvas(self.figure)
33
34        # this is the Navigation widget
35        # it takes the Canvas widget and a parent
36        self.toolbar = NavigationToolbar(self.canvas, self)
37
38        # set the layout
39        layout = QtGui.QVBoxLayout()
40        layout.addWidget(self.canvas)
41        layout.addWidget(self.toolbar)
42        self.setLayout(layout)
43
44        # defaults
45        self._current_plot = 111
46        self._data = []
47        self._qx_data = []
48        self._qy_data = []
49
50        # default color map
51        self._cmap = DEFAULT_CMAP
52
53        self._ax = self.figure.add_subplot(self._current_plot)
54
55        # Notify the helper
56        PlotHelper.addPlot(self)
57        # Notify the listeners
58        self.parent.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
59
60    def data(self, data=None):
61        """ data setter """
62        self._data = data.data
63        self._qx_data=data.qx_data
64        self._qy_data=data.qy_data
65        self._xmin=data.xmin
66        self._xmax=data.xmax
67        self._ymin=data.ymin
68        self._ymax=data.ymax
69        self._zmin=data.zmin
70        self._zmax=data.zmax
71        self._color=0
72        self._symbol=0
73        self._label=data.name
74        self._scale = 'linear'
75
76        self.x_label(xlabel=data._xaxis + data._xunit)
77        self.y_label(ylabel=data._yaxis + data._yunit)
78        self.title(title=data.title)
79
80    def title(self, title=""):
81        """ title setter """
82        self._title = title
83
84    def id(self, id=""):
85        """ id setter """
86        self._id = id
87
88    def x_label(self, xlabel=""):
89        """ x-label setter """
90        self._xlabel = r'$%s$'% xlabel
91
92    def y_label(self, ylabel=""):
93        """ y-label setter """
94        self._ylabel = r'$%s$'% ylabel
95
96    def clean(self):
97        """
98        Redraw the graph
99        """
100        self.figure.delaxes(self._ax)
101        self._ax = self.figure.add_subplot(self._current_plot)
102
103    def plot(self, marker=None, linestyle=None):
104        """
105        Plot 2D self._data
106        """
107        # create an axis
108        ax = self._ax
109
110        # graph properties
111        ax.set_xlabel(self._xlabel)
112        ax.set_ylabel(self._ylabel)
113        ax.set_title(label=self._title)
114
115        # Re-adjust colorbar
116        # self.figure.subplots_adjust(left=0.2, right=.8, bottom=.2)
117
118        output = self._build_matrix()
119
120        im = ax.imshow(output,
121                       interpolation='nearest',
122                       origin='lower',
123                       vmin=self._zmin, vmax=self._zmax,
124                       cmap=self._cmap,
125                       extent=(self._xmin, self._xmax,
126                               self._ymin, self._ymax))
127
128        cbax = self.figure.add_axes([0.84, 0.2, 0.02, 0.7])
129        cb = self.figure.colorbar(im, cax=cbax)
130        cb.update_bruteforce(im)
131        cb.set_label('$' + self._scale + '$')
132
133        # Schedule the draw for the next time the event loop is idle.
134        self.canvas.draw_idle()
135
136    def closeEvent(self, event):
137        """
138        Overwrite the close event adding helper notification
139        """
140        # Please remove me from your database.
141        PlotHelper.deletePlot(PlotHelper.idOfPlot(self))
142        # Notify the listeners
143        self.parent.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
144        event.accept()
145
146    def _build_matrix(self):
147        """
148        Build a matrix for 2d plot from a vector
149        Returns a matrix (image) with ~ square binning
150        Requirement: need 1d array formats of
151        self.data, self._qx_data, and self._qy_data
152        where each one corresponds to z, x, or y axis values
153
154        """
155        # No qx or qy given in a vector format
156        if self._qx_data == None or self._qy_data == None \
157                or self._qx_data.ndim != 1 or self._qy_data.ndim != 1:
158            # do we need deepcopy here?
159            return self._data
160
161        # maximum # of loops to fillup_pixels
162        # otherwise, loop could never stop depending on data
163        max_loop = 1
164        # get the x and y_bin arrays.
165        self._get_bins()
166        # set zero to None
167
168        #Note: Can not use scipy.interpolate.Rbf:
169        # 'cause too many data points (>10000)<=JHC.
170        # 1d array to use for weighting the data point averaging
171        #when they fall into a same bin.
172        weights_data = numpy.ones([self._data.size])
173        # get histogram of ones w/len(data); this will provide
174        #the weights of data on each bins
175        weights, xedges, yedges = numpy.histogram2d(x=self._qy_data,
176                                                    y=self._qx_data,
177                                                    bins=[self.y_bins, self.x_bins],
178                                                    weights=weights_data)
179        # get histogram of data, all points into a bin in a way of summing
180        image, xedges, yedges = numpy.histogram2d(x=self._qy_data,
181                                                  y=self._qx_data,
182                                                  bins=[self.y_bins, self.x_bins],
183                                                  weights=self._data)
184        # Now, normalize the image by weights only for weights>1:
185        # If weight == 1, there is only one data point in the bin so
186        # that no normalization is required.
187        image[weights > 1] = image[weights > 1] / weights[weights > 1]
188        # Set image bins w/o a data point (weight==0) as None (was set to zero
189        # by histogram2d.)
190        image[weights == 0] = None
191
192        # Fill empty bins with 8 nearest neighbors only when at least
193        #one None point exists
194        loop = 0
195
196        # do while loop until all vacant bins are filled up up
197        #to loop = max_loop
198        while not(numpy.isfinite(image[weights == 0])).all():
199            if loop >= max_loop:  # this protects never-ending loop
200                break
201            image = self._fillup_pixels(image=image, weights=weights)
202            loop += 1
203
204        return image
205
206    def _get_bins(self):
207        """
208        get bins
209        set x_bins and y_bins into self, 1d arrays of the index with
210        ~ square binning
211        Requirement: need 1d array formats of
212        self._qx_data, and self._qy_data
213        where each one corresponds to  x, or y axis values
214        """
215        # No qx or qy given in a vector format
216        if self._qx_data == None or self._qy_data == None \
217                or self._qx_data.ndim != 1 or self._qy_data.ndim != 1:
218            return self._data
219
220        # find max and min values of qx and qy
221        xmax = self._qx_data.max()
222        xmin = self._qx_data.min()
223        ymax = self._qy_data.max()
224        ymin = self._qy_data.min()
225
226        # calculate the range of qx and qy: this way, it is a little
227        # more independent
228        x_size = xmax - xmin
229        y_size = ymax - ymin
230
231        # estimate the # of pixels on each axes
232        npix_y = int(numpy.floor(numpy.sqrt(len(self._qy_data))))
233        npix_x = int(numpy.floor(len(self._qy_data) / npix_y))
234
235        # bin size: x- & y-directions
236        xstep = x_size / (npix_x - 1)
237        ystep = y_size / (npix_y - 1)
238
239        # max and min taking account of the bin sizes
240        xmax = xmax + xstep / 2.0
241        xmin = xmin - xstep / 2.0
242        ymax = ymax + ystep / 2.0
243        ymin = ymin - ystep / 2.0
244
245        # store x and y bin centers in q space
246        x_bins = numpy.linspace(xmin, xmax, npix_x)
247        y_bins = numpy.linspace(ymin, ymax, npix_y)
248
249        #set x_bins and y_bins
250        self.x_bins = x_bins
251        self.y_bins = y_bins
252
253    def _fillup_pixels(self, image=None, weights=None):
254        """
255        Fill z values of the empty cells of 2d image matrix
256        with the average over up-to next nearest neighbor points
257
258        :param image: (2d matrix with some zi = None)
259
260        :return: image (2d array )
261
262        :TODO: Find better way to do for-loop below
263
264        """
265        # No image matrix given
266        if image == None or numpy.ndim(image) != 2 \
267                or numpy.isfinite(image).all() \
268                or weights == None:
269            return image
270        # Get bin size in y and x directions
271        len_y = len(image)
272        len_x = len(image[1])
273        temp_image = numpy.zeros([len_y, len_x])
274        weit = numpy.zeros([len_y, len_x])
275        # do for-loop for all pixels
276        for n_y in range(len(image)):
277            for n_x in range(len(image[1])):
278                # find only null pixels
279                if weights[n_y][n_x] > 0 or numpy.isfinite(image[n_y][n_x]):
280                    continue
281                else:
282                    # find 4 nearest neighbors
283                    # check where or not it is at the corner
284                    if n_y != 0 and numpy.isfinite(image[n_y - 1][n_x]):
285                        temp_image[n_y][n_x] += image[n_y - 1][n_x]
286                        weit[n_y][n_x] += 1
287                    if n_x != 0 and numpy.isfinite(image[n_y][n_x - 1]):
288                        temp_image[n_y][n_x] += image[n_y][n_x - 1]
289                        weit[n_y][n_x] += 1
290                    if n_y != len_y - 1 and numpy.isfinite(image[n_y + 1][n_x]):
291                        temp_image[n_y][n_x] += image[n_y + 1][n_x]
292                        weit[n_y][n_x] += 1
293                    if n_x != len_x - 1 and numpy.isfinite(image[n_y][n_x + 1]):
294                        temp_image[n_y][n_x] += image[n_y][n_x + 1]
295                        weit[n_y][n_x] += 1
296                    # go 4 next nearest neighbors when no non-zero
297                    # neighbor exists
298                    if n_y != 0 and n_x != 0 and\
299                         numpy.isfinite(image[n_y - 1][n_x - 1]):
300                        temp_image[n_y][n_x] += image[n_y - 1][n_x - 1]
301                        weit[n_y][n_x] += 1
302                    if n_y != len_y - 1 and n_x != 0 and \
303                        numpy.isfinite(image[n_y + 1][n_x - 1]):
304                        temp_image[n_y][n_x] += image[n_y + 1][n_x - 1]
305                        weit[n_y][n_x] += 1
306                    if n_y != len_y and n_x != len_x - 1 and \
307                        numpy.isfinite(image[n_y - 1][n_x + 1]):
308                        temp_image[n_y][n_x] += image[n_y - 1][n_x + 1]
309                        weit[n_y][n_x] += 1
310                    if n_y != len_y - 1 and n_x != len_x - 1 and \
311                        numpy.isfinite(image[n_y + 1][n_x + 1]):
312                        temp_image[n_y][n_x] += image[n_y + 1][n_x + 1]
313                        weit[n_y][n_x] += 1
314
315        # get it normalized
316        ind = (weit > 0)
317        image[ind] = temp_image[ind] / weit[ind]
318
319        return image
Note: See TracBrowser for help on using the repository browser.