source: sasview/src/sas/qtgui/PlotUtilities.py @ 239214f

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

Remove graph properties - MPL dialog is enough.
Minor fix in Plot Properties (WP review)

  • Property mode set to 100644
File size: 9.5 KB
Line 
1import sys
2import numpy
3from collections import OrderedDict
4
5# MPL shapes dictionary with some extra styles rendered internally.
6# Ordered for consistent display in combo boxes
7SHAPES = OrderedDict([
8        ('Circle' , 'o'),
9        ('Point' , '.'),
10        ('Pixel' , ','),
11        ('Triangle Down' , 'v'),
12        ('Triangle Up' , '^'),
13        ('Triangle Left' , '<'),
14        ('Triangle Right' , '>'),
15        ('Octagon' , '8'),
16        ('Square' , 's'),
17        ('Pentagon' , 'p'),
18        ('Star' , '*'),
19        ('Hexagon1' , 'h'),
20        ('Hexagon2' , 'H'),
21        ('Cross +' , 'p'),
22        ('Cross X ' , 'x'),
23        ('Diamond' , 'D'),
24        ('Thin Diamond' , 'd'),
25        ('Line' , '-'),
26        ('Dash' , '--'),
27        ('Vline' , 'vline'),
28        ('Step' , 'step'),
29])
30
31# MPL Colors dictionary. Ordered for consistent display
32COLORS = OrderedDict([
33        ('Blue', 'b'),
34        ('Green', 'g'),
35        ('Red', 'r'),
36        ('Cyan', 'c'),
37        ('Magenta', 'm'),
38        ('Yellow', 'y'),
39        ('Black', 'k'),
40        ('Custom', 'x'),
41])
42
43def build_matrix(data, qx_data, qy_data):
44    """
45    Build a matrix for 2d plot from a vector
46    Returns a matrix (image) with ~ square binning
47    Requirement: need 1d array formats of
48    data, qx_data, and qy_data
49    where each one corresponds to z, x, or y axis values
50
51    """
52    # No qx or qy given in a vector format
53    if qx_data is None or qy_data is None \
54            or qx_data.ndim != 1 or qy_data.ndim != 1:
55        return data
56
57    # maximum # of loops to fillup_pixels
58    # otherwise, loop could never stop depending on data
59    max_loop = 1
60    # get the x and y_bin arrays.
61    x_bins, y_bins = get_bins(qx_data, qy_data)
62    # set zero to None
63
64    #Note: Can not use scipy.interpolate.Rbf:
65    # 'cause too many data points (>10000)<=JHC.
66    # 1d array to use for weighting the data point averaging
67    #when they fall into a same bin.
68    weights_data = numpy.ones([data.size])
69    # get histogram of ones w/len(data); this will provide
70    #the weights of data on each bins
71    weights, xedges, yedges = numpy.histogram2d(x=qy_data,
72                                                y=qx_data,
73                                                bins=[y_bins, x_bins],
74                                                weights=weights_data)
75    # get histogram of data, all points into a bin in a way of summing
76    image, xedges, yedges = numpy.histogram2d(x=qy_data,
77                                                y=qx_data,
78                                                bins=[y_bins, x_bins],
79                                                weights=data)
80    # Now, normalize the image by weights only for weights>1:
81    # If weight == 1, there is only one data point in the bin so
82    # that no normalization is required.
83    image[weights > 1] = image[weights > 1] / weights[weights > 1]
84    # Set image bins w/o a data point (weight==0) as None (was set to zero
85    # by histogram2d.)
86    image[weights == 0] = None
87
88    # Fill empty bins with 8 nearest neighbors only when at least
89    #one None point exists
90    loop = 0
91
92    # do while loop until all vacant bins are filled up up
93    #to loop = max_loop
94    while not(numpy.isfinite(image[weights == 0])).all():
95        if loop >= max_loop:  # this protects never-ending loop
96            break
97        image = fillupPixels(image=image, weights=weights)
98        loop += 1
99
100    return image
101
102def get_bins(qx_data, qy_data):
103    """
104    get bins
105    return x_bins and y_bins: 1d arrays of the index with
106    ~ square binning
107    Requirement: need 1d array formats of
108    qx_data, and qy_data
109    where each one corresponds to  x, or y axis values
110    """
111    # No qx or qy given in a vector format
112    if qx_data is None or qy_data is None \
113            or qx_data.ndim != 1 or qy_data.ndim != 1:
114        return data
115
116    # find max and min values of qx and qy
117    xmax = qx_data.max()
118    xmin = qx_data.min()
119    ymax = qy_data.max()
120    ymin = qy_data.min()
121
122    # calculate the range of qx and qy: this way, it is a little
123    # more independent
124    x_size = xmax - xmin
125    y_size = ymax - ymin
126
127    # estimate the # of pixels on each axes
128    npix_y = int(numpy.floor(numpy.sqrt(len(qy_data))))
129    npix_x = int(numpy.floor(len(qy_data) / npix_y))
130
131    # bin size: x- & y-directions
132    xstep = x_size / (npix_x - 1)
133    ystep = y_size / (npix_y - 1)
134
135    # max and min taking account of the bin sizes
136    xmax = xmax + xstep / 2.0
137    xmin = xmin - xstep / 2.0
138    ymax = ymax + ystep / 2.0
139    ymin = ymin - ystep / 2.0
140
141    # store x and y bin centers in q space
142    x_bins = numpy.linspace(xmin, xmax, npix_x)
143    y_bins = numpy.linspace(ymin, ymax, npix_y)
144
145    #set x_bins and y_bins
146    return x_bins, y_bins
147
148def fillupPixels(image=None, weights=None):
149    """
150    Fill z values of the empty cells of 2d image matrix
151    with the average over up-to next nearest neighbor points
152
153    :param image: (2d matrix with some zi = None)
154
155    :return: image (2d array )
156
157    :TODO: Find better way to do for-loop below
158
159    """
160    # No image matrix given
161    if image == None or numpy.ndim(image) != 2 \
162            or numpy.isfinite(image).all() \
163            or weights == None:
164        return image
165    # Get bin size in y and x directions
166    len_y = len(image)
167    len_x = len(image[1])
168    temp_image = numpy.zeros([len_y, len_x])
169    weit = numpy.zeros([len_y, len_x])
170    # do for-loop for all pixels
171    for n_y in range(len(image)):
172        for n_x in range(len(image[1])):
173            # find only null pixels
174            if weights[n_y][n_x] > 0 or numpy.isfinite(image[n_y][n_x]):
175                continue
176            else:
177                # find 4 nearest neighbors
178                # check where or not it is at the corner
179                if n_y != 0 and numpy.isfinite(image[n_y - 1][n_x]):
180                    temp_image[n_y][n_x] += image[n_y - 1][n_x]
181                    weit[n_y][n_x] += 1
182                if n_x != 0 and numpy.isfinite(image[n_y][n_x - 1]):
183                    temp_image[n_y][n_x] += image[n_y][n_x - 1]
184                    weit[n_y][n_x] += 1
185                if n_y != len_y - 1 and numpy.isfinite(image[n_y + 1][n_x]):
186                    temp_image[n_y][n_x] += image[n_y + 1][n_x]
187                    weit[n_y][n_x] += 1
188                if n_x != len_x - 1 and numpy.isfinite(image[n_y][n_x + 1]):
189                    temp_image[n_y][n_x] += image[n_y][n_x + 1]
190                    weit[n_y][n_x] += 1
191                # go 4 next nearest neighbors when no non-zero
192                # neighbor exists
193                if n_y != 0 and n_x != 0 and\
194                        numpy.isfinite(image[n_y - 1][n_x - 1]):
195                    temp_image[n_y][n_x] += image[n_y - 1][n_x - 1]
196                    weit[n_y][n_x] += 1
197                if n_y != len_y - 1 and n_x != 0 and \
198                    numpy.isfinite(image[n_y + 1][n_x - 1]):
199                    temp_image[n_y][n_x] += image[n_y + 1][n_x - 1]
200                    weit[n_y][n_x] += 1
201                if n_y != len_y and n_x != len_x - 1 and \
202                    numpy.isfinite(image[n_y - 1][n_x + 1]):
203                    temp_image[n_y][n_x] += image[n_y - 1][n_x + 1]
204                    weit[n_y][n_x] += 1
205                if n_y != len_y - 1 and n_x != len_x - 1 and \
206                    numpy.isfinite(image[n_y + 1][n_x + 1]):
207                    temp_image[n_y][n_x] += image[n_y + 1][n_x + 1]
208                    weit[n_y][n_x] += 1
209
210    # get it normalized
211    ind = (weit > 0)
212    image[ind] = temp_image[ind] / weit[ind]
213
214    return image
215
216def rescale(lo, hi, step, pt=None, bal=None, scale='linear'):
217    """
218    Rescale (lo,hi) by step, returning the new (lo,hi)
219    The scaling is centered on pt, with positive values of step
220    driving lo/hi away from pt and negative values pulling them in.
221    If bal is given instead of point, it is already in [0,1] coordinates.
222
223    This is a helper function for step-based zooming.
224    """
225    # Convert values into the correct scale for a linear transformation
226    # TODO: use proper scale transformers
227    loprev = lo
228    hiprev = hi
229    if scale == 'log':
230        assert lo > 0
231        if lo > 0:
232            lo = numpy.log10(lo)
233        if hi > 0:
234            hi = numpy.log10(hi)
235        if pt is not None:
236            pt = numpy.log10(pt)
237
238    # Compute delta from axis range * %, or 1-% if persent is negative
239    if step > 0:
240        delta = float(hi - lo) * step / 100
241    else:
242        delta = float(hi - lo) * step / (100 - step)
243
244    # Add scale factor proportionally to the lo and hi values,
245    # preserving the
246    # point under the mouse
247    if bal is None:
248        bal = float(pt - lo) / (hi - lo)
249    lo = lo - (bal * delta)
250    hi = hi + (1 - bal) * delta
251
252    # Convert transformed values back to the original scale
253    if scale == 'log':
254        if (lo <= -250) or (hi >= 250):
255            lo = loprev
256            hi = hiprev
257        else:
258            lo, hi = numpy.power(10., lo), numpy.power(10., hi)
259    return (lo, hi)
260
261def getValidColor(color):
262    '''
263    Returns a valid matplotlib color
264    '''
265
266    if color is not None:
267        # Check if it's an int
268        if isinstance(color, int):
269            # Check if it's within the range
270            if 0 <= color <=6:
271                color = COLORS.values()[color]
272        # Check if it's an RGB string
273        elif isinstance(color, str):
274            # Assure the correctnes of the string
275            assert(color[0]=="#" and len(color) == 7)
276            import string
277            assert(all(c in string.hexdigits for c in color[1:]))
278        else:
279            raise AttributeError
280
281    return color
Note: See TracBrowser for help on using the repository browser.