Scale a 2D array logarithmically in Python - python

I have a 2D array of spectrogram data which I am scaling with scikit-image for display in a web browser. I would like to scale the array logarithmically in the y-axis."
I can plot the data logarithmically in the y-axis using Matplotlib, but I want access to the 2D array representation of this newly scaled image, but Matplotlib only provides the original, unscaled array. Scikit-image scales 2D arrays linearly, but not logarithmically.
# plot a log-scaled z
w, h = z.shape
fig, ax = plt.subplots()
ax.set_yscale('symlog')
mesh = ax.pcolormesh(np.arange(h+1), np.arange(w+1), z)
# get the array of z
logimg = mesh.get_array().reshape(mesh._meshHeight, mesh._meshWidth)

Let's start with some example data:
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pylab as plt
# some 2D example data
x, y = np.arange(30)+1, np.arange(20)+1
x_grid, y_grid = np.meshgrid(x, y)
z_grid = np.cos(2*np.pi*x_grid/10) + np.sin(2*np.pi*y_grid/4)
# Graph 1
plt.pcolormesh(x, np.log(y), z_grid);
plt.xlabel('x'); plt.ylabel('log(y) (non regular spacing)');
Here is a graph with log(y) as vertical axis. The sampling along y is then non uniform (the data are unchanged, they are only drawn on a deformed grid):
In order to get deformed data on a regular grid, an interpolation is performed between log(y) and a new regular y grid:
# Interpolation of the transformed data on a regular new y axis
ylog_interpolation = interp1d(np.log(y), z_grid.T)
new_y = np.linspace(min(np.log(y)), max(np.log(y)), len(y))
new_z_grid = ylog_interpolation(new_y).T
# axis
plt.pcolormesh(x, new_y, new_z_grid);
plt.xlabel('x'); plt.ylabel('new y (regular spacing)');
Now, the grid is regular but the data are deformed, new_z_grid can be exported as an image.

Related

matplotlib logarithmic colormap for logarithmic surface plot

I'm using python to create a 3D surface map, I have an array of data I'm trying to plot as a 3D surface, the issue is that I have logged the Z axis (necessary to show peaks in data) which means the default colormap doesn't work (displays one continous color). I've tried using the LogNorm to normalise the colormap but again this produces one continous color. I'm not sure whether I should be using the logged values to normalise the map, but if i do this the max is negative and produces an error?
fig=plt.figure(figsize=(10,10))
ax=plt.axes(projection='3d')
def log_tick_formatter(val, pos=None):
return "{:.2e}".format(10**val)
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
X=np.arange(0,2,1)
Y=np.arange(0,3,1)
X,Y=np.meshgrid(X,Y)
Z=[[1.2e-11,1.3e-11,-1.8e-11],[6e-13,1.3e-13,2e-15]]
Z_min=np.amin(Z)
Z_max=np.amax(Z)
norm = colors.LogNorm(vmin=1e-15,vmax=(Z_max),clip=False)
ax.plot_surface(X,Y,np.transpose(np.log10(Z)),norm=norm,cmap='rainbow')
Just an example of the logarithmic colors and logarithmic data:
#!/usr/bin/env ipython
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
import matplotlib.colors as colors
# ------------------
X=np.arange(0,401,1);nx= np.size(X)
Y=np.arange(40,200,1);ny = np.size(Y)
X,Y=np.meshgrid(X,Y)
Z = 10000*np.random.random((ny,nx))
Z=np.array(Z)
# ------------------------------------------------------------
Z_min=np.amin(Z)
Z_max=np.amax(Z)
# ------------------------------------------------------------
norm = colors.LogNorm(vmin=np.nanmin(Z),vmax=np.nanmax(Z),clip=False)
# ------------------------------------------------------------
fig = plt.figure(figsize=(15,5));axs = [fig.add_subplot(131),fig.add_subplot(132),fig.add_subplot(133)]
p0 = axs[0].pcolormesh(X,Y,np.log10(Z),cmap='rainbow',norm=norm);plt.colorbar(p0,ax=axs[0]);
axs[0].set_title('Original method: NOT TO DO!')
p1 = axs[1].pcolormesh(X,Y,Z,cmap='rainbow',norm=norm);plt.colorbar(p1,ax=axs[1])
axs[1].set_title('Normalized colorbar, original data')
p2 = axs[2].pcolormesh(X,Y,np.log10(Z),cmap='rainbow');plt.colorbar(p2,ax=axs[2])
axs[2].set_title('Logarithmic data, original colormap')
plt.savefig('test.png',bbox_inches='tight')
# --------------------------------------------------------------
So the result is like this:
In the first case, we have used logarithmic colormap and also taken the logarithm of the data, so the colorbar does not work anymore as the values on the map are small and we have used large limits for the colorbar.
In the middle image, we use the normalized colorbar or logarithmic colorbar so that it is quite natively understood what is on the image and what are the values. The third case is when we take the logarithm from the data and the colorbar is just showing the power of the 10th we have to use in order to interpret the coloured value on the plot.
So, in the end, I would suggest the middle method: use the logarithmic colorbar and original data.
Edit: to solve your problem you are taking the log of the data then you are taking it again when calculating the norm, simply remove the norm and apply vmin and vmax directly to the drawing function
ax.plot_surface(X, Y, np.transpose(np.log10(Z)), cmap='rainbow',vmin=np.log10(1e-15),vmax=np.log10(Z_max))
you can use the facecolor argument of plot_surface to define color for each face independent of z, here's a simplified example
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
x = np.linspace(0,10,100)
y = np.linspace(0,10,100)
x,y = np.meshgrid(x,y)
z = np.sin(x+y)
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
cmap = matplotlib.cm.get_cmap('rainbow')
def rescale_0_to_1(item):
max_z = np.amax(item)
min_z = np.amin(item)
return (item - min_z)/(max_z-min_z)
rgba = cmap(rescale_0_to_1(z)) # some values of z to calculate color with
real_z = np.log(z+1) # real values of z to draw
surf = ax.plot_surface(x, y, real_z, cmap='rainbow', facecolors=rgba)
plt.show()
you can modify it to calculate colors based on x or y or something completely unrelated.

colormap from a matrix in python

I have a 2D output matrix (say, Z) which was calculated as a function of two variables x,y.
x varies in a non-uniform manner like [1e-5,5e-5,1e-4,5e-4,1e-3,5e-3,1e-2]
y varies in a uniform manner like [300,400,500,600,700,800]
[ say, Z = np.random.rand(7,6) ]
I was trying to plot a colormap of the matrix Z by first creating a meshgrid for x,y and then using the pcolormesh. Since, my x values are non-uniform, I do not kn ow how to proceed. Any inputs would be greatly appreciated.
No need for meshgrids; regarding the non-uniform axes: In your case a log-scale works fine:
import numpy as np
from matplotlib import pyplot as plt
x = [1e-5,5e-5,1e-4,5e-4,1e-3,5e-3,1e-2]
y = [300,400,500,600,700,800]
# either enlarge x and y by one number (right-most
# endpoint for those bins), or make Z smaller as I did
Z = np.random.rand(6,5)
fig = plt.figure()
ax = fig.gca()
ax.pcolormesh(x,y,Z.T)
ax.set_xscale("log")
fig.show()

How do I interpolate a 2D gridded point cloud to a continuous area?

I have a 2 dimensional Numpy NDarray filled with floats between 0 and about 8. This 2 dimensional arrays size is (1000, 1600) and there are about 1400 values, (the points in the point cloud), the remaining values are None, so matplotlib does not plot these values. You can see the plotted table in the image below. What I'd like to have is, the None-values interpolated with the values next to it to have a gradientlike heatmap. This pointcloud represents the shape of a roof and I want to process this data to an image I can give into a neural network to detect the type of roof.
The code I used for this plot is pretty short,
import matplotlib.pyplot as plt
plt.clf()
#plotGrid is the numpy.ndarray with shape (1000, 1600) and dtype float
plt.imshow(plotGrid, cmap='gray', interpolation='nearest')
plt.colorbar()
plt.show()
Image (click to enlarge and see points):
tricontourf
You might use a tricontour / tricontourf plot of the valid values. To this end, you first need to filter out all nan values (you should indeed make the invalid values np.nan instead of None).
Those values, together with their coordinates can be put into plt.tricontourf() to obtain a contour plot without the need of manual interpolation.
import matplotlib.pyplot as plt
import numpy as np
# Generate some example data
f = lambda x,y : np.exp((-(x-150)**2-(y-150)**2)/3.e3)
plotGrid = np.zeros((300,300))*np.nan
coo = np.random.randint(5,295, size=(150,2) )
for x,y in coo:
plotGrid[y,x] = f(x,y)
#plotGrid is now a numpy.ndarray with shape (300,300), mostly np.nan, and dtype float
# filter out nan values and get coordinates.
x,y = np.indices(plotGrid.shape)
x,y,z = x[~np.isnan(plotGrid)], y[~np.isnan(plotGrid)], plotGrid[~np.isnan(plotGrid)]
plt.tricontourf(x,y,z)
plt.colorbar()
plt.show()
tripcolor
Using tripcolor is another option then:
plt.tripcolor(x,y,z, shading='gouraud')
interpolate and contourf
You can also interpolate the data on a grid first, using matplotlib.mlab.griddata, and then either use a normal contourf plot,
xi = np.linspace(0, plotGrid.shape[1], plotGrid.shape[1])
yi = np.linspace(0, plotGrid.shape[0], plotGrid.shape[0])
zi = mlab.griddata(x, y, z, xi, yi, interp='linear')
plt.contourf(xi, yi, zi, 15)
interpolate and imshow
Or in the same manner use an imshow plot,
plt.imshow(zi)
I think scipy.interpolate.interp2d does what you need:
import scipy.interpolate
z_all = plotGrid.astype(float) # convert nones to nan
x_all, y_all = np.indices(plotGrid.shape) # get x and y coordinates
# convert to 1d arrays of coordinates
valid = ~np.isnan(z_all)
x, y, z = x_all[valid], y_all[valid], z_all[valid]
# interpolate
interp = scipy.interpolate.interp2d(x, y, z)
filled_data = interp(x_all[:,0], y_all[0,:]) # this is kinda gross, but `interp` doesn't
# do normal broadcasting
plt.imshow(filled_data)

How to do 4D plot in matplotlib without np.meshgrid?

I know that I can do a 4D plot in matplotlib with the following code, with the fourth dimension shown as a colormap:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
fig = plt.figure()
ax = fig.add_subplot(111,projection= '3d' )
x = np.arange(100)/ 101
y = np.sin(x) + np.cos(x)
X,Y = np.meshgrid(x,y)
Z = (X**2) / (Y**2)
A = np.sin(Z)
ax.plot_surface(X,Y, Z, facecolors=cm.Oranges(A))
plt.show()
But what if my data is not a function of the other data? How do I do this without np.meshgrid? (In other words, my Z series cannot be a function of the output of the X,Y which is the output of np.meshgrid(x,y), because Z is not a function of X and Y.)
A surface plot is a mapping of 2D points to a 1D value, i.e. for each pair of (x,y) coordinates you need exactly one z coordinate. So while it isn't strictly necessary to have Z being a function of X and Y, those arrays to plot need to have the same number of elements.
For a plot_surface the restriction is to have X and Y as gridded 2D data. Z does not have to be 2D but needs to have the same number of elements.
This requirement can be weakened using a plot_trisurf where the only requirement is that there is a strict mapping of x,y,z, i.e. the ith value in X and Y corresponds to the ith value in Z.
In any case, even if there is no analytic function to map X and Y to Z, Z still needs to be some kind of mapping. Otherwise it is even questionable what information the resulting plot would convey.

Griding with python

I am trying to plot a picture like this in python.
I have three parameters for ploting.
x:
[ 0.03570416 0.05201517 0.05418171 0.01868341 0.07116423 0.07547471]
y:
[-0.32079484 -0.53330218 -1.02866859 -0.94808545 -0.51682506 -0.26788337]
z:
[-0.32079484 -0.53330218 -1.02866859 -0.94808545 -0.51682506 -0.26788337]
so x is x-axis and y is y-axis. however z is the intensity of the pixel.
I come up with this code:
z = np.array(reals)
x = np.array(ra)
y = np.array(dec)
nrows, ncols = 10, 10
grid = z.reshape((nrows, ncols))
plt.imshow(grid, extent=(x.min(), x.max(), y.max(), y.min()), interpolation='nearest', cmap=cm.gist_rainbow)
plt.title('This is a phase function')
plt.xlabel('ra')
plt.ylabel('dec')
plt.show()
However I get this error:
grid = z.reshape((nrows, ncols))
ValueError: total size of new array must be unchanged
ra, dec and reals are normal arrays with the same size. I calculated them before and then I create the numpy arrays with them
The data you show is not consistent with making an image, but you could make a scatter plot with it.
The two basic types of plots for z values at (x,y) coordinate pairs are:
scatter plots, where for each (x,y) pair, a z-value is specified.
image (imshow, pcolor, pcolormesh, contour), where an x-axis with m regularly spaced values, and a y-axis with n regularly spaced values are specified, and then an array of z-values with size (m,n) is given.
Your data looks more like the former type, so I'm suggesting a scatter plot.
Here's what a scatter plot looks like (btw, your y and z values are the same, which if probably a mistake):
import numpy as np
import matplotlib.pyplot as plt
x = np.array([ 0.03570416, 0.05201517, 0.05418171, 0.01868341, 0.07116423, 0.07547471])
y = np.array([-0.32079484, -0.53330218, -1.02866859, -0.94808545, -0.51682506, -0.26788337])
z = np.array([-0.32079484, -0.53330218, -1.02866859, -0.94808545, -0.51682506, -0.26788337])
plt.scatter(x, y, c=z, s =250)
plt.show()

Categories