Different luminance of Python imshow with transposed data - python

This might be a trivial question.
I store a series of spectrum with 1025 frequency bins into a list, using which I want to plot by imshow. The data is a list having 345 entries representing the number of time frames, each of which has 1025 dimensions representing frequency bins. The normal or conventional way to display the spectrogram is having x-axis as time frame and y-axis as frequency bin.
My attempts are as follows:
imshow(X, aspect='auto');show()
imshow(np.array(X), aspect='auto');show() # Seems to be same as the first one.
# The correct display would be x-axis as time and y-axis as frequency bin,
# and the y-axis should be ordered from lower to upper.
imshow(np.array(X).T, aspect='auto', origin='lower');show()
However, the third plot seems to have dimmer luminance and it could be the issue of normalization. How does imshow behave differently with transposed data?
EDIT:
Trying to specify the figure size at the first place
plt.figure(figsize=(7,5))
imshow(np.array(X).T, aspect='auto', origin='lower')
Though the figure size alters the luminance of the image, the relative magnitude of each component in y-axis doesn't look to be the same for me when compared to the former image, i.e., the first and second one. How exactly does imshow adjust the luminance with transposed data or different orientation?

Related

Contour Plot of Binary Data (0 or 1)

I have x values, y values, and z values. The z values are either 0 or 1, essentially indicating whether an (x,y) pair is a threat (1) or not a threat (0).
I have been trying to plot a 2D contour plot using the matplotlib contourf. This seems to have been interpolating between my z values, which I don't want. So, I did a bit of searching and found that I could use pcolormesh to better plot binary data. However, I am still having some issues.
First, the colorbar of my pcolormesh plot doesn't show two distinct colors (white or red). Instead, it shows a full spectrum from white to red. See the attached plot for what I mean. How do I change this so that the colorbar only shows two colors, for 0 and 1? Second, is there a way to draw a grid of squares into the contour plot so that it is more clear for which x and y intervals the 0s and 1s are occurring. Third, my code calls for minorticks. However, these do not show up in the plot. Why?
The code which I use is shown here. The vels and ms for x and y can really be anything, and the threat_bin is just the corresponding 0 or 1 values for all the (vets,ms) pairs:
fig=plt.figure(figsize=(6,5))
ax2=fig.add_subplot(111)
from matplotlib import cm
XX,YY=np.meshgrid(vels, ms)
cp=ax2.pcolormesh(XX/1000.0,YY,threat_bin, cmap=cm.Reds)
ax2.minorticks_on()
ax2.set_ylabel('Initial Meteoroid Mass (kg)')
ax2.set_xlabel('Initial Meteoroid Velocity (km/s)')
ax2.set_yscale('log')
fig.colorbar(cp, ticks=[0,1], label='Threat Binary')
plt.show()
Please be simple with your recommendations, and let me know the code I should include or change with respect to what I have at the moment.

Python plt.contour colorbar

I am trying to do a plot of a seismic wave using plt.contour.
I have 3 arrays:
time (x-axis)
frequency (y-axis)
amplitude (z-axis)
This is my results so far:
The problem is that I want to change the scaling of the colorbar: making a gradation and not having this white color when the amplitude is low. But I am not able to do so, even though I spent a lot of time browsing the doc.
I read that plt.pcolormesh is not appropriate here (it is just working here because I am in a special case), but this what I want to get regarding to the colours and colorbar:
This is the code I wrote:
T = len(time[0])*(time[0][1] - time[0][0]) # multiply ampFFT with T to offset
Z = abs(ampFFT)*(T) # abbreviation
# freq = frequency, ampFFT = Fast Fourier Transform of the amplitude of the wave
# freq, amFFT and time have same dimensions: 40 x 1418 (40 steps of time discretization x steps to have the total time. 2D because it is easier to use)
maxFreq = abs(freq).max() # maxium frequency for plot boundaries
maxAmpFFT = abs(Z).max()/2 # maxium ampFFT for plot boundaries of colorbar divided by 2 to scale better with the colors
minAmpFFT = abs(Z).min()
plt.figure(1)
plt.contour(time, freq, Z, vmin=minAmpFFT, vmax=maxAmpFFT)
plt.colorbar()
plt.ylim(0,maxFreq) # 0 to remove the negative frequencies useless here
plt.title("Amplitude intensity regarding to time and frequency")
plt.xlabel('time (in secondes)')
plt.ylabel('frequency (in Hz)')
plt.show()
Thank you for your attention!
NB : In case you were wondering about plt.pcolormesh: the plot is completely messed up when I choose to increase the time discretization (here I split the time in 40, but when I split the time in 1000 the plot is not correct, and I want to be able to split the time in smaller pieces).
EDIT: When I use plt.contourf instead of plt.contour I got this plot:
Which is not really convincing either. I understand why the yellow colour takes so much space (it is because I set a low vmax), but I don't understand why there is still white colour in my plot.
EDIT 2: My teacher plotted my data, and I have the correct data. The only problem that is left is the white background in my plot (and the deep blue on left and right border for nor apparent reason when I use plt.contourf). Despite those problems, the highest amplitude is located around 0.5 Hz, which is in agreement with the work of my teacher.
He used gnuplot, but since I don't know gnuplot, I prefer to use python.
Solution/Workaround I found
Here is what I did to display my data like countourf does, but without the display problems:
Explanation: for the surface, I took abs(freq) instead of just freq because I have negative frequencies.
It is because that when calculating the frequency of a FFT, you have a frequency that repeat itself a 2nd time like this:
You have 2 way of obtaining this frequency:
- the frequency is positive, this array is 2 x Nyquist frequency (so if you divide the array by 2, you have all your wave, and it doesn't repeat itself).
- the frequency starts negative and go to positive, this array also is 2 x Nyquist frequency (so if you remove the negative value you have all your wave, and it doesn't repeat itself).
Python fft.fftfreq use the 2nd option. plot_surface doesn't work well with removing the data of an array (for me it was still displayed). So I made the frequency value absolute and the problem disappeared.
fig = plt.figure(1, figsize=(18,15)) # figsize: increase plot size
ax = fig.gca(projection='3d')
surf = ax.plot_surface(time, abs(freq), Z, rstride=1, cstride=1, cmap=cm.magma, linewidth=0, antialiased=False, vmin=minAmpFFT, vmax=maxAmpFFT)
ax.set_zlim(0, maxAmpFFT)
ax.set_ylim(0, maxFreq)
ax.view_init(azim=90, elev=90) # change view to top view, with axis in the right direction
plt.title("Amplitude intensity (m/Hz^0.5) regarding to time and frequency")
plt.xlabel('x : time (in secondes)')
plt.ylabel('y : frequency (in Hz)')
# ax.yaxis._set_scale('log') # should be in log, but does not work
plt.gca().invert_xaxis() # invert x axis !! MUST BE AFTER X,Y,Z LIM
plt.gca().invert_yaxis() # invert y axis !! MUST BE AFTER X,Y,Z LIM
plt.colorbar(surf)
fig.tight_layout()
plt.show()
This is the plot I got:

plt.quiver() plotting dots instead of vectors in some places

I'm currently analyzing some data by creating a vector plot. All the vectors have length 1 unit. Most show up fine, but certain vectors such as:
fig = plt.figure()
plt.axes(xlim=(-24, 24), ylim=(0, 150))
plt.quiver([-19.1038], [96.5851], [-19.1001+19.1038], [97.5832-96.5851],angles='xy', scale_units='xy', scale=1, headwidth=1, headlength=10, minshaft=5)
plt.show()
show up as a point. (Please note that I am not drawing my vectors individually like this; I only drew this particular one to try to debug my code.) This appears to only be occurring for nearly vertical vectors. I've also noticed that this issue is resolved if I "zoom in" on the vector (i.e. change the axis scaling). However, I cannot do that as many other vectors in my plot will be outside of the domain/range. Is there another way to fix this?
The problem is demonstrated in the below figure:
There are two components to your problem, and both have to do with how you chose to represent your data.
The default behaviour of quiver is to auto-scale your vectors to a reasonable size for a pretty result. The documentation says as much:
The default settings auto-scales the length of the arrows to a reasonable size. To change this behavior see the scale and scale_units kwargs.
And then
scale_units : [ ‘width’ | ‘height’ | ‘dots’ | ‘inches’ | ‘x’ | ‘y’ | ‘xy’ ], None, optional
[...]
If scale_units is ‘x’ then the vector will be 0.5 x-axis units. To plot vectors in the x-y plane, with u and v having the same units as x and y, use angles='xy', scale_units='xy', scale=1.
So in your case, you're telling quiver to plot the arrow in xy data units. Since your arrow is of unit length, it is drawn as a 1-length arrow. Your data limits, on the other hand, are huge: 40 units wide, 150 units tall. On this scale a length-1 arrow is just too small, and matplotlib decides to truncate the arrow and plot a dot instead.
If you zoom in, as you said yourself, the arrow appears. If we remove the parameters that turn your arrow into a toothpick, it turns out that the arrow you plot is perfectly fine if you look close enough (not the axes):
Now, the question is why this behaviour depends on the orientation of your vectors. The reason for this behaviour is that the x and y limits are different in your plot, so a unit-length horizontal line and a unit-length vertical line contain a different number of pixels (since your data is scaled in xy units). This implies that while horizontal arrows are long enough to be represented accurately, vertical ones become so short that matplotlib decides to truncate them to dots, which shouldn't be too obvious with the default arrow format, but it is pretty bad with your custom arrows. Your use case is such that the rendering cut-off used by matplotlib happens to fall between the length of your horizontal vectors and the length of your vertical ones.
You have two straightforward choices. One is to increase the scaling for your arrows to the point where every orientation is represented accurately. This would probably be the solution to Y in a small XY problem here. What you should really do, is represent your data accurately. Since you're plotting your vector field in xy data units, you presumably want your x and y axes to have equal sizes, and you want your arrows to have visually unit length (i.e. a length that's independent from their orientation).
So I suggest that you force your plot to have equal units on both axes, at the cost of ending up with a rectangular figure:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.axis('scaled') # <-- key addition
ax.axis([-24, 24, 0, 150])
ax.quiver([-19.1038], [96.5851], [-19.1001+19.1038], [97.5832-96.5851],
angles='xy', scale_units='xy', scale=1, headwidth=1,
headlength=10, minshaft=5)
plt.show()
Trust me: there's a tiny arrow in there. The main point is that this way either all of your vectors will be dots (if you're zoomed out too much), or neither of them will. Then you have a sane situation, and can choose the overall scaling of your vectors accordingly.

Interpret numpy.fft.fft2 output

My goal is to obtain a plot with the spatial frequencies of an image - kind of like doing a fourier transformation on it. I don't care about the position on the image of features with the frequency f (for instance); I'd just like to have a graphic which tells me how much of every frequency I have (the amplitude for a frequency band could be represented by the sum of contrasts with that frequency).
I am trying to do this via the numpy.fft.fft2 function.
Here is a link to a minimal example portraying my use case.
As it turns out I only get distinctly larger values for frequencies[:30,:30], and of these the absolute highest value is frequencies[0,0]. How can I interpret this?
What exactly does the amplitude of each value stand for?
What does it mean that my highest value is in frequency[0,0] What is a 0 Hz frequency?
Can I bin the values somehow so that my frequency spectrum is orientation agnostic?
freq has a few very large values, and lots of small values. You can see that by plotting
plt.hist(freq.ravel(), bins=100)
(See below.) So, when you use
ax1.imshow(freq, interpolation="none")
Matplotlib uses freq.min() as the lowest value in the color range (which is by default colored blue), and freq.max() as the highest value in the color range (which is by default colored red). Since almost all the values in freq are near the blue end, the plot as a whole looks blue.
You can get a more informative plot by rescaling the values in freq so that the low values are more widely distributed on the color range.
For example, you can get a better distribution of values by taking the log of freq. (You probably don't want to throw away the highest values, since they correspond to frequencies with the highest power.)
import matplotlib as ml
import matplotlib.pyplot as plt
import numpy as np
import Image
file_path = "data"
image = np.asarray(Image.open(file_path).convert('L'))
freq = np.fft.fft2(image)
freq = np.abs(freq)
fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(14, 6))
ax[0,0].hist(freq.ravel(), bins=100)
ax[0,0].set_title('hist(freq)')
ax[0,1].hist(np.log(freq).ravel(), bins=100)
ax[0,1].set_title('hist(log(freq))')
ax[1,0].imshow(np.log(freq), interpolation="none")
ax[1,0].set_title('log(freq)')
ax[1,1].imshow(image, interpolation="none")
plt.show()
From the docs:
The output, analogously to fft, contains the term for zero frequency
in the low-order corner of the transformed axes,
Thus, freq[0,0] is the "zero frequency" term. In other words, it is the constant term in the discrete Fourier Transform.

Changing canvas size dynamically

I want to plot multiple time-series (each time-series in its own plot) using the plot()-method of matplotlib.
X-Axis: Time
Y-Axis: Parameter-Value
As the time-series have different lengths, I want to resize the canvas along the X-Axis dynamically, so that the time-series do not get stretched/compressed dependent on their total length. The size of the whole figure should stay the same, independent of the time-series-length. I know how to modify the figure size using
rcParams['figure.figsize'] = width, height
but I do only want to modify the canvas size (the part of the figure where the time-series is actually plotted in). Is there a similar way of just changing the figure's canvas?
I think you want to change the dimensions of the axes that your time-series is being plotted in, rather than the dimensions of the figure canvas (which as far as I'm aware can't be altered without changing the overall figure size).
You can do this using ax.set_position(), which takes a tuple of (left, bottom, width, height) values in normalized canvas coordinates between 0 and 1.
from pylab import *
nr = 4
nc = 1
fig,axes = subplots(nr,nc,sharex=True)
The sharex keyword tells the subplots to keep their x limits the same. Replace plot in your application with axes[ith index].plot, etc.

Categories