Set specific aspect ratio for narrow matrix - Matplotlib - python

I have a 13x1340 matrix that I usually plot correctly without the need to specify an aspect ratio.
However, I would now like to tweak that aspect ratio so that two matrices whose 13 rows correspond to different scales are plotted as rectangles of equal length but different height, proportionally to the corresponding axis scale.
I have tried to use the get_aspect() method to obtain the numerical value that is being used, but it returns 'auto'. I have tried to guess the value and found that it is close to 4.5/(1340*180), which looks like a completely absurd value to me. I expected it to be something closer to 13/1340, but perhaps I don't quite understand how aspect ratios are calculated.
Setting the aspect ratio to 1 gives me an incredibly thin figure, with the proper vertical size. As the value decreases, the figure becomes longer in length, until it reaches ~ 4.5/(1340*180). After that, it starts losing height while keeping a fixed length.
Figure size is set to 3 inches high by 7 inches large, and the dpi is set to 300 on the savefig() method.
The get_data_ratio() method returns a value slightly larger than 13*1340, although it is clear that this value is not the aspect ratio used do construct the figure.

Related

The smallest valid alpha value in matplotlib?

Some of my plots have several million lines. I dynamically adjust the alpha value, by the number of lines, so that the outliers more or less disappear, while the most prominent features appear clear. But for some alpha's, the lines just disappear.
What is the smallest valid alpha value for line plots in in matplotlib? And why is there a lower limit?
As #ImportanceOfBeingErnest suggested in the comments, the lower limit seems to be 1/255.
I did not have time to go though the source code and all, but I did test it, and assume what happens is, that the input alpha value needs to be represented as an int between 0 and 255:
int(alpha*255)
When the input alpha value is smaller than 1/255, e.g. 1/256, it is therefore represented by a 0, and the plot lines disappear. Whereas when the alpha is 1/255 (or slightly larger), it is converted to 1, and the plot lines can be seen.
My guess would also have been 1./255 such that the maximum 8-bit RGB color multiplied with alpha still makes a non-zero contribution to the image. However, that would also only allow to draw lines with fully saturated colors, and in reality it is not true.
Changing the relevant value in
for i in range(1000):
plt.plot((0, 100), (0, 100), alpha = 1/510, color = "g")
I found that the limit is actually 510 (i.e., the minimum alpha is 1./510). This holds true also for non-saturated colors (e.g., "wheat"), so reality is obviously still more complicated than the naive assumption described above.
My matplotlib version is 3.4.2.
I haven't investigated this further -- for very large numbers of overlaid images one would have to come up with a different approach anyway. There is a related ticket on github that suggests using mplcairo. Another option would be to export the output images as numpy arrays so that one can manually add and normalize them.
There's no lower limit; the lines just appear to be invisible for very small alpha values.
If you draw one line with alpha=0.01 the difference in color is too small for your screen / eyes to discern. If you draw 100 lines with a=0.01 on top of each other, you will see them.
As for your problem, you can just add a small number to the alpha value of each draw call so that lines that would otherwise have alpha < 0.1 still appear.

Rebinning numpy array by non-integer factor

I have a grid of model spectra, which have a constant, very high spectral resolution, and I need to down-sample them to a lower resolution, while preserving the total number of counts.
In essence, if the first 5 bins have (nominal center-of-bin) wavelengths [7.8, 7.81, 7.82, 7.83, 7.84], and the values [1.01, 1.02, 1.015, 1.014, 1.02], and my desired bins are some (non-integer) factor (say, 2.5) times as wide, I want my new spectrum to have nominal wavelengths [7.81, 7.83] and values [1.01+1.02+0.5*1.015, 0.5*1.015+1.014+1.02] (in general, though, the bins are not lined up as well, so you may get fractions of bins on either side).
I'll call my grid spec_ssp, and it has a shape of (93, 16, 39848). Wavelength varies along axis 2, and the first two axes are other parameters. I also have the nominal (central) wavelengths for each wavelength bin (technically, they're the log of the wavelength, but that shouldn't matter), called logL_ssp, and the desired new spacing of the logL grid, dlogL_new. I can figure out the nominal logL spacing of my templates dlogL_ssp by calculating np.median(logL_ssp[1:] - logL_ssp[:-1]), and it's about 20% the desired logL spacing. We'll call that fraction f.
I originally tried to use scipy.ndimage.zoom, using the aforementioned factor f, but discovered that it gives me an array that's downsampled by a factor of exactly 4. I need an exact resampling, so this won't work.
Next, I tried linearly interpolating along axis 2 using np.interp1d, after setting up new bin limits, with the aim of integrating the spectra in my grid using np.integrate.quad between successive bin limits, effectively getting an estimate of the total light in each of my new bins, more or less rigorously. However, quad doesn't play nicely with interp1d's interpolators (quad doesn't like non-float inputs). And, since I have ~1500 model spectra, the whole thing takes ages to run while iterating over all three axes (yes, I'm only making a new interpolator once per model spectrum).
Any ideas how to tackle this?

matplotlib: get axis ratio of plot

I need to produce scatter plots for several 2D data sets automatically.
By default the aspect ratio is set ax.set_aspect(aspect='equal'), which most of the times works because the x,y values are distributed more or less in a squared region.
Sometimes though, I encounter a data set that, when plotted with the equal ratio, looks like this:
i.e.: too narrow in a given axis. For the above image, the axis are approximately 1:8.
In such a case, an aspect ratio of ax.set_aspect(aspect='auto') would result in a much better plot:
Now, I don't want to set aspect='auto' as my default for all data sets because using aspect='equal' is actually the correct way of displaying such a scatter plot.
I need to fall back to using ax.set_aspect(aspect='auto') only for cases such as the one above.
The question: is there a way to know before hand if the aspect ratio of a plot will be too narrow if aspect='equal' is used? Like getting the actual aspect ratio of the plotted data set.
This way, based on such a number, I can adjust the aspect ratio to something more sane looking (i.e.: auto or some other aspect ratio) instead of 'equal'.
Something like this ought to do,
aspect = (max(x) - min(x)) / (max(y) - min(y))
The axes method get_data_ratio gives the aspect ratio of the bounds of your data as displayed.¹
ax.get_data_ratio()
for example:
M = 4.0
ax.set_aspect('equal' if 1/M < ax.get_data_ratio() < M else 'auto')
¹This is the reciprocal of #farenorth's answer when the axes are zoomed right around the data, i.e., when max(y) == max(ax.get_ylim()) since it is calculated using the ranges in ax.get_ybound and ax.get_xbound.

matplotlib radar plot min values

I started with the matplotlib radar example but values below some min values disappear.
I have a gist here.
The result looks like
As you can see in the gist, the values for D and E in series A are both 3 but they don't show up at all.
There is some scaling going on.
In order to find out what the problem is I started with the original values and removed one by one.
When I removed one whole series then the scale would shrink.
Here an example (removing Factor 5) and scale in [0,0.2] range shrinks.
From
to
I don't care so much about the scaling but I would like my values at 3 score to show up.
Many thanks
Actually, the values for D and E in series A do show up, although they are plotted in the center of the plot. This is because the limits of your "y-axis" is autoscaled.
If you want to have a fixed "minimum radius", you can simply put ax.set_ylim(bottom=0) in your for-loop.
If you want the minimum radius to be a number relative to the lowest plotted value, you can include something like ax.set_ylim(np.asarray(data.values()).flatten().min() - margin) in the for-loop, where margin is the distance from the lowest plotted value to the center of the plot.
With fixed center at radius 0 (added markers to better show that the points are plotted):
By setting margin = 1, and using the relative y-limits, I get this output:

Selecting best range of values from histogram curve

Scenario :
I am trying to track two different colored objects. At the beginning, user is prompted to hold the first colored object (say, may be a RED) at a particular position in front of camera (marked on screen by a rectangle) and press any key, then my program takes that portion of frame (ROI) and analyze the color in it, to find what color to track. Similarly for second object also. Then as usual, use cv.inRange function in HSV color plane and track the object.
What is done :
I took the ROI of object to be tracked, converted it to HSV and checked the Hue histogram. I got two cases as below :
( here there is only one major central peak. But in some cases, I get two such peaks, One a bigger peak with some pixel cluster around it, and second peak, smaller than first one, but significant size with small cluster around it also. I don't have an sample image of it now. But it almost look like below (created in paint))
Question :
How can I get best range of hue values from these histograms?
By best range I mean, may be around 80-90% of the pixels in ROI lie in that range.
Or is there any better method than this to track different colored objects ?
If I understand right, the only thing you need here is to find a maximum in a graph, where the maximum is not necessarily the highest peak, but the area with largest density.
Here's a very simple not too scientific but fast O(n) approach. Run the histogram trough a low pass filter. E.g. a moving average. The length of your average can be let's say 20. In that case the 10th value of your new modified histogram would be:
mh10 = (h1 + h2 + ... + h20) / 20
where h1, h2... are values from your histogram. The next value:
mh11 = (h2 + h3 + ... + h21) / 20
which can be calculated much easier using the previously calculated mh10, by dropping it's first component and adding a new one to the end:
mh11 = mh10 - h1/20 + h21/20
Your only problem is how you handle numbers at the edge of your histogram. You could shrink your moving average's length to the length available, or you could add values before and after what you already have. But either way, you couldn't handle peaks right at the edge.
And finally, when you have this modified histogram, just get the maximum. This works, because now every value in your histogram contains not only himself but it's neighbors as well.
A more sophisticated approach is to weight your average for example with a Gaussian curve. But that's not linear any more. It would be O(k*n), where k is the length of your average which is also the length of the Gaussian.

Categories