source: sasview/src/sas/qtgui/PlotUtilities.py @ 87cc73a

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

Added Modify Plot Properties functionality. SASVIEW-169

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