import sys
import numpy
import string
from collections import OrderedDict
# MPL shapes dictionary with some extra styles rendered internally.
# Ordered for consistent display in combo boxes
SHAPES = OrderedDict([
('Circle' , 'o'),
('Point' , '.'),
('Pixel' , ','),
('Triangle Down' , 'v'),
('Triangle Up' , '^'),
('Triangle Left' , '<'),
('Triangle Right' , '>'),
('Octagon' , '8'),
('Square' , 's'),
('Pentagon' , 'p'),
('Star' , '*'),
('Hexagon1' , 'h'),
('Hexagon2' , 'H'),
('Cross +' , 'p'),
('Cross X ' , 'x'),
('Diamond' , 'D'),
('Thin Diamond' , 'd'),
('Line' , '-'),
('Dash' , '--'),
('Vline' , 'vline'),
('Step' , 'step'),
])
# MPL Colors dictionary. Ordered for consistent display
COLORS = OrderedDict([
('Blue', '#1f77b4'),
('Green', '#2ca02c'),
('Red', '#d62728'),
('Cyan', '#17becf'),
('Magenta', '#e377c2'),
('Yellow', '#bcbd22'),
('Black', '#000000'),
('Custom', 'x'),
])
[docs]def build_matrix(data, qx_data, qy_data):
"""
Build a matrix for 2d plot from a vector
Returns a matrix (image) with ~ square binning
Requirement: need 1d array formats of
data, qx_data, and qy_data
where each one corresponds to z, x, or y axis values
"""
# No qx or qy given in a vector format
if qx_data is None or qy_data is None \
or qx_data.ndim != 1 or qy_data.ndim != 1:
return data
# maximum # of loops to fillup_pixels
# otherwise, loop could never stop depending on data
max_loop = 1
# get the x and y_bin arrays.
x_bins, y_bins = get_bins(qx_data, qy_data)
# set zero to None
#Note: Can not use scipy.interpolate.Rbf:
# 'cause too many data points (>10000)<=JHC.
# 1d array to use for weighting the data point averaging
#when they fall into a same bin.
weights_data = numpy.ones([data.size])
# get histogram of ones w/len(data); this will provide
#the weights of data on each bins
weights, xedges, yedges = numpy.histogram2d(x=qy_data,
y=qx_data,
bins=[y_bins, x_bins],
weights=weights_data)
# get histogram of data, all points into a bin in a way of summing
image, xedges, yedges = numpy.histogram2d(x=qy_data,
y=qx_data,
bins=[y_bins, x_bins],
weights=data)
# Now, normalize the image by weights only for weights>1:
# If weight == 1, there is only one data point in the bin so
# that no normalization is required.
image[weights > 1] = image[weights > 1] / weights[weights > 1]
# Set image bins w/o a data point (weight==0) as None (was set to zero
# by histogram2d.)
image[weights == 0] = None
# Fill empty bins with 8 nearest neighbors only when at least
#one None point exists
loop = 0
# do while loop until all vacant bins are filled up up
#to loop = max_loop
while not(numpy.isfinite(image[weights == 0])).all():
if loop >= max_loop: # this protects never-ending loop
break
image = fillupPixels(image=image, weights=weights)
loop += 1
return image
[docs]def get_bins(qx_data, qy_data):
"""
get bins
return x_bins and y_bins: 1d arrays of the index with
~ square binning
Requirement: need 1d array formats of
qx_data, and qy_data
where each one corresponds to x, or y axis values
"""
# No qx or qy given in a vector format
if qx_data is None or qy_data is None \
or qx_data.ndim != 1 or qy_data.ndim != 1:
return data
# find max and min values of qx and qy
xmax = qx_data.max()
xmin = qx_data.min()
ymax = qy_data.max()
ymin = qy_data.min()
# calculate the range of qx and qy: this way, it is a little
# more independent
x_size = xmax - xmin
y_size = ymax - ymin
# estimate the # of pixels on each axes
npix_y = int(numpy.floor(numpy.sqrt(len(qy_data))))
npix_x = int(numpy.floor(len(qy_data) / npix_y))
# bin size: x- & y-directions
xstep = x_size / (npix_x - 1)
ystep = y_size / (npix_y - 1)
# max and min taking account of the bin sizes
xmax = xmax + xstep / 2.0
xmin = xmin - xstep / 2.0
ymax = ymax + ystep / 2.0
ymin = ymin - ystep / 2.0
# store x and y bin centers in q space
x_bins = numpy.linspace(xmin, xmax, npix_x)
y_bins = numpy.linspace(ymin, ymax, npix_y)
#set x_bins and y_bins
return x_bins, y_bins
[docs]def fillupPixels(image=None, weights=None):
"""
Fill z values of the empty cells of 2d image matrix
with the average over up-to next nearest neighbor points
:param image: (2d matrix with some zi = None)
:return: image (2d array )
:TODO: Find better way to do for-loop below
"""
# No image matrix given
if image is None or numpy.ndim(image) != 2 \
or numpy.isfinite(image).all() \
or weights is None:
return image
# Get bin size in y and x directions
len_y = len(image)
len_x = len(image[1])
temp_image = numpy.zeros([len_y, len_x])
weit = numpy.zeros([len_y, len_x])
# do for-loop for all pixels
for n_y in range(len(image)):
for n_x in range(len(image[1])):
# find only null pixels
if weights[n_y][n_x] > 0 or numpy.isfinite(image[n_y][n_x]):
continue
else:
# find 4 nearest neighbors
# check where or not it is at the corner
if n_y != 0 and numpy.isfinite(image[n_y - 1][n_x]):
temp_image[n_y][n_x] += image[n_y - 1][n_x]
weit[n_y][n_x] += 1
if n_x != 0 and numpy.isfinite(image[n_y][n_x - 1]):
temp_image[n_y][n_x] += image[n_y][n_x - 1]
weit[n_y][n_x] += 1
if n_y != len_y - 1 and numpy.isfinite(image[n_y + 1][n_x]):
temp_image[n_y][n_x] += image[n_y + 1][n_x]
weit[n_y][n_x] += 1
if n_x != len_x - 1 and numpy.isfinite(image[n_y][n_x + 1]):
temp_image[n_y][n_x] += image[n_y][n_x + 1]
weit[n_y][n_x] += 1
# go 4 next nearest neighbors when no non-zero
# neighbor exists
if n_y != 0 and n_x != 0 and\
numpy.isfinite(image[n_y - 1][n_x - 1]):
temp_image[n_y][n_x] += image[n_y - 1][n_x - 1]
weit[n_y][n_x] += 1
if n_y != len_y - 1 and n_x != 0 and \
numpy.isfinite(image[n_y + 1][n_x - 1]):
temp_image[n_y][n_x] += image[n_y + 1][n_x - 1]
weit[n_y][n_x] += 1
if n_y != len_y and n_x != len_x - 1 and \
numpy.isfinite(image[n_y - 1][n_x + 1]):
temp_image[n_y][n_x] += image[n_y - 1][n_x + 1]
weit[n_y][n_x] += 1
if n_y != len_y - 1 and n_x != len_x - 1 and \
numpy.isfinite(image[n_y + 1][n_x + 1]):
temp_image[n_y][n_x] += image[n_y + 1][n_x + 1]
weit[n_y][n_x] += 1
# get it normalized
ind = (weit > 0)
image[ind] = temp_image[ind] / weit[ind]
return image
[docs]def rescale(lo, hi, step, pt=None, bal=None, scale='linear'):
"""
Rescale (lo,hi) by step, returning the new (lo,hi)
The scaling is centered on pt, with positive values of step
driving lo/hi away from pt and negative values pulling them in.
If bal is given instead of point, it is already in [0,1] coordinates.
This is a helper function for step-based zooming.
"""
# Convert values into the correct scale for a linear transformation
# TODO: use proper scale transformers
loprev = lo
hiprev = hi
if scale == 'log':
assert lo > 0
if lo > 0:
lo = numpy.log10(lo)
if hi > 0:
hi = numpy.log10(hi)
if pt is not None:
pt = numpy.log10(pt)
# Compute delta from axis range * %, or 1-% if persent is negative
if step > 0:
delta = float(hi - lo) * step / 100
else:
delta = float(hi - lo) * step / (100 - step)
# Add scale factor proportionally to the lo and hi values,
# preserving the
# point under the mouse
if bal is None:
bal = float(pt - lo) / (hi - lo)
lo = lo - (bal * delta)
hi = hi + (1 - bal) * delta
# Convert transformed values back to the original scale
if scale == 'log':
if (lo <= -250) or (hi >= 250):
lo = loprev
hi = hiprev
else:
lo, hi = numpy.power(10., lo), numpy.power(10., hi)
return (lo, hi)
[docs]def getValidColor(color):
'''
Returns a valid matplotlib color
'''
if color is None:
return color
# Check if it's an int
if isinstance(color, int):
# Check if it's within the range
if 0 <= color <=6:
color = list(COLORS.values())[color]
elif isinstance(color, str):
# This could be a one letter code
if len(color) == 1:
if not color in list (COLORS.values()):
raise AttributeError
else:
# or an RGB string
assert(color[0]=="#" and len(color) == 7)
assert(all(c in string.hexdigits for c in color[1:]))
else:
raise AttributeError
return color