Is it possible for matplotlib only update the newest point to the figure instead of re-draw the whole figure?
For example: this may be the fastest way for dynamic plotting
initiate:
fig1 = Figure(figsize = (8.0,8.0),dpi = 100)
axes1 = fig1.add_subplot(111)
line1, = axes1.plot([],[],animated = True)
when new data is coming:
line1.set_data(new_xarray,new_yarray)
axes1.draw_artist(line1)
fig1.canvas.update()
fig1.canvas.flush_events()
But this will re-draw the whole figure! I'm think whether this is possible:
when new data is coming:
axes1.draw_only_last_point(new_x,new_y)
update_the_canvas()
It will only add this new point(new_x,new_y) to the axes instead of re-draw every point.
And if you know which graphic library for python can do that, please answer or comment, thank you so much!!!!!
Really appreciate your help!
Is only redrawing the entire figure the problem, i.e. it is ok to redraw the line itself as long as the figure is unchanged? Is the data known beforehand?
If the answer to those questions are NO, and YES, then it might be worth looking into the animate-class for matplotlib. One example where the data is known beforehand, but the points are plotted one by one is this example. In the example, the figure is redrawn if the newest point is outside of the current x-lim. If you know the range of your data you can avoid it by setting the limits beforehand.
You might also want to look into this answer, the animate example list or the animate documentation.
this is my (so far) little experience.
I started some month ago with Python(2.x) and openCV (2.4.13) as graphic library.I found in may first project that openCV for python works with numpy structure as much as matplotlib and (with slight difference) they can work together.
I had to update some pixel after some condition. I first did my elaboration from images with opencv obtaining a numpy 2D array, like a matrix.
The trick is: opencv mainly thinks about input as images, in terms of X as width first, then Y as height. The numpy structure wants rows and columns wich in fact is Y before X.
With this in mind I updated pixel by pixel the image-matrix A and plot it again with a colormap
import matplotlib as plt
import cv2
A = cv2.imread('your_image.png',0) # 0 means grayscale
# now you loaded an image in a numpy array A
for every new x,y pixel
A[y,x] = new pixel intensity value
plot = plt.imshow(A, 'CMRmap')
plt.show()
If you want images again, consider use this
import matplotlib.image as mpimg
#previous code
mpimg.imsave("newA.png", A)
If you want to work with colors remember that images in colour are X by Y by 3 numpy array but matplotlib has RGB as the right order of channels, openCv works with BGR order. So
C = cv2.imread('colour_reference.png',1) # 1 means BGR
A[y,x,0] = newRedvalue = C[y,x][2]
A[y,x,1] = newGreenvalue = C[y,x][1]
A[y,x,2] = newBluevalue = C[y,x][0]
I hope this will help you in some way
Related
Hello there!
I am trying to create a figure consisting of a chloropleth map and a bar plot in Matplotlib. To achieve this, i am using the Geopandas library alongside Pandas and Matplotlib. I've run into an interesting problem that i couldn't find any answer for on the internet. Here's the problem:
This link leads to an image that replicates the problem.
As it can be seen on the image above, the map on the top (generated by Geopandas) does not span the same width as the bar chart on the bottom. There is too much whitespace to the left and the right of the figure. I want to get rid of this whitespace and make the map fit horizontally on the space that is allocated to it. I am also leaving a code sample below for those who wish to recreate it:
fig = plt.figure(figsize = (25.60,14.40)) #Here, i am setting the overall figure size
ax_1 = fig.add_subplot(2,1,1) #This will be the map
istanbul_districts.plot(ax = ax_1,
edgecolor = "black",
alpha = 1,
color = "Red") #Istanbul_districts is a GeoDataFrame object.
ax_2 = fig.add_subplot(2,1,2)
labels = list(health.loc[:,"district_eng"].value_counts().sort_values(ascending = False).index)
from numpy import arange
bar_positions = arange(len(labels)) + 1
bar_heights = h_inst_per_district_eng.loc[:,"health_count"].values.astype(int)
ax_2.bar(bar_positions,bar_heights,
width = 0.7,
align = "center",
color = "blue") #This is a generic barplot from Matplotlib
I am leaving a second image that shows the end result of the code snippet above:
This link also leads to an image that replicates the problem.
It can be clearly seen above that the axes of the two subplots do not start and end on the same location. Perhaps that could be the problem? What can be done to make them the same size?
Thanks to all those answer for their time in advance!
Adding an explanation, since you have found one solution.
If you specify matplotlib figure with two axes in a way you did, you get the figure split in half. Both axes are the same. Let's say that the original ratio of the figure is 1:1, your axes will be both 1:2.
This arbitrary ratio is fine for a bar chart, which can be scaled to essentially any ratio. It does not matter much if it is horizontal or vertical (from a plotting perspective, not data-viz).
However, if you want your map to show correct non-distorted shapes, you can't just specify the aspect ratio. That just follows the data. So if you have a map, which bounding box has 1:1 ratio, you can't expect that it will fill the whole 1:2 axis. GeoPandas changes the aspect ratio to follow the map's ratio.
The reason why the first example leaves gaps on side and the "solution" does not is this. Because the leftover space is on top and on the bottom the axis, it is not shown in the solution. Because it is on sides in the issue, it just stays there. If you had your plots next to each other instead of above, it would be vice versa.
Hope it is clearer.
Hello again!
swatchai's comment set me up on the right track and i found the culprit. Simply adjusting the figsize to a value like (19,19) fixed the problem. I'd still be happy if anyone can explain exactly why this happens.
Here's what it looks like when the figsize is a square (19,19):
Thanks for your efforts!
I am using Python 2.7 and need to draw a time series using matplotlib library. My y axis data is numeric and everything is ok with it.
The problem is my x axis data which is not numeric, and matplotlib does not cooperate in this case. It does not draw me a time series even though it is not supposed to affect the correctness of the plot, because the x axis data is arranged by a given order anyway and it's order does not affect anything logically.
For example let's say the x data is ["i","like","python"] and the y axis data is [1,2,3].
I did not add my code because I've found that the code is ok, it works if I change the data to all numeric data.
Please explain me how can I use matplotlib to draw the time series, without making me to convert the x values to numeric stuff.
I've based my matplotlib code on following answers: How to plot Time Series using matplotlib Python, Time Series Plot Python.
Matplotlib requires someway of positioning those labels. See the following example:
import matplotlib.pyplot as plt
x = ["i","like","python"]
y = [1,2,3]
plt.plot(y,y) # y,y because both are numeric (you could create an xt = [1,2,3]
plt.xticks(y,x) # same here, the second argument are the labels.
plt.show()
, that results in this:
Notice how I've put the labels there but had to somehow say where they are supposed to be.
I also think you should put a part of your code so that it's easier for other people to suggest upon.
I would like to plot a set of points using pyplot in matplotlib but have none of the points be on the edge of my axes. The autoscale (or something) sets the xlim and ylim such that often the first and last points lie at x = xmin or xmax making it difficult to read in some situations.
This is more often problematic with loglog() or semilog() plots because the autoscale would like xmin and xmax to be exact powers of ten, but if my data contains only three points, e.g. at xdata = [10**2,10**3,10**4] then the first and last points will lie on the border of the plot.
Attempted Workaround
This is my solution to add a 10% buffer to either side of the graph. But is there a way to do this more elegantly or automatically?
from numpy import array, log10
from matplotlib.pyplot import *
xdata = array([10**2,10**3,10**4])
ydata = xdata**2
figure()
loglog(xdata,ydata,'.')
xmin,xmax = xlim()
xbuff = 0.1*log10(xmax/xmin)
xlim(xmin*10**(-xbuff),xmax*10**(xbuff))
I am hoping for a one- or two-line solution that I can easily use whenever I make a plot like this.
Linear Plot
To make clear what I'm doing in my workaround, I should add an example in linear space (instead of log space):
plot(xdata,ydata)
xmin,xmax = xlim()
xbuff = 0.1*(xmax-xmin)
xlim(xmin-xbuff,xmax+xbuff))
which is identical to the previous example but for a linear axis.
Limits too large
A related problem is that sometimes the limits are too large. Say my data is something like ydata = xdata**0.25 so that the variance in the range is much less than a decade but ends at exactly 10**1. Then, the autoscale ylim are 10**0 to 10**1 though the data are only in the top portion of the plot. Using my workaround above, I can increase ymax so that the third point is fully within the limits but I don't know how to increase ymin so that there is less whitespace at the lower portion of my plot. i.e., the point is that I don't always want to spread my limits apart but would just like to have some constant (or proportional) buffer around all my points.
#askewchan I just succesfully achieved how to change matplotlib settings by editing matplotlibrc configuration file and running python directly from terminal. Don't know the reason yet, but matplotlibrc is not working when I run python from spyder3 (my IDE). Just follow steps here matplotlib.org/users/customizing.html.
1) Solution one (default for all plots)
Try put this in matplotlibrc and you will see the buffer increase:
axes.xmargin : 0.1 # x margin. See `axes.Axes.margins`
axes.ymargin : 0.1 # y margin See `axes.Axes.margins`
Values must be between 0 and 1.
Obs.: Due to bugs, scale is not correctly working yet. It'll be fixed for matplotlib 1.5 (mine is 1.4.3 yet...). More info:
axes.xmargin/ymargin rcParam behaves differently than pyplot.margins() #2298
Better auto-selection of axis limits #4891
2) Solution two (individually for each plot inside the code)
There is also the margins function (for put directly in the code). Example:
import numpy as np
from matplotlib import pyplot as plt
t = np.linspace(-6,6,1000)
plt.plot(t,np.sin(t))
plt.margins(x=0.1, y=0.1)
plt.savefig('plot.png')
Obs.: Here scale is working (0.1 will increase 10% of buffer before and after x-range and y-range).
A similar question was posed to the matplotlib-users list earlier this year. The most promising solution involves implementing a Locator (based on MaxNLocator in this case) to override MaxNLocator.view_limits.
I attach a zip archive with all the files needed to illustrate and reproduce the problem.
(I don't have permissions to upload images yet...)
I have an image (test2.png in the zip archive ) with curved lines.
I try to warp it so the lines are straight.
I thought of using scikit-image transform, and in particular transform.PolynomialTransform because the transformation involves high order distortions.
So first I measure the precise position of each line at regular intervals in x to define the input interest points (in the file source_test2.csv).
Then I compute the corresponding desired positions, located along a straight line (in the file destination_test2.csv).
The figure correspondence.png shows how it looks like.
Next, I simply call transform.PolynomialTransform() using a polynomial of order 3.
It finds a solution, but when I apply it using transform.warp(), the result is crazy, as illustrated in the file Crazy_Warped.png
Anybody can tell what I am doing wrong?
I tried polynomial of order 2 without luck...
I managed to get a good transformation for a sub-image (the first 400 columns only).
Is transform.PolynomialTransform() completely unstable in a case like mine?
Here is the entire code:
import numpy as np
import matplotlib.pyplot as plt
import asciitable
import matplotlib.pylab as pylab
from skimage import io, transform
# read image
orig=io.imread("test2.png",as_grey=True)
# read tables with reference points and their desired transformed positions
source=asciitable.read("source_test2.csv")
destination=asciitable.read("destination_test2.csv")
# format as numpy.arrays as required by scikit-image
# (need to add 1 because I started to count positions from 0...)
source=np.column_stack((source["x"]+1,source["y"]+1))
destination=np.column_stack((destination["x"]+1,destination["y"]+1))
# Plot
plt.imshow(orig, cmap='gray', interpolation='nearest')
plt.plot(source[:,0],source[:,1],'+r')
plt.plot(destination[:,0],destination[:,1],'+b')
plt.xlim(0,orig.shape[1])
plt.ylim(0,orig.shape[0])
# Compute the transformation
t = transform.PolynomialTransform()
t.estimate(destination,source,3)
# Warping the image
img_warped = transform.warp(orig, t, order=2, mode='constant',cval=float('nan'))
# Show the result
plt.imshow(img_warped, cmap='gray', interpolation='nearest')
plt.plot(source[:,0],source[:,1],'+r')
plt.plot(destination[:,0],destination[:,1],'+b')
plt.xlim(0,img_warped.shape[1])
plt.ylim(0,img_warped.shape[0])
# Save as a file
io.imsave("warped.png",img_warped)
Thanks in advance!
There are a couple of things wrong here, mainly they have to do with coordinate conventions. For example, if we examine the code where you plot the original image, and then put the clicked point on top of it:
plt.imshow(orig, cmap='gray', interpolation='nearest')
plt.plot(source[:,0],source[:,1],'+r')
plt.xlim(0,orig.shape[1])
plt.ylim(0,orig.shape[0])
(I've taken out the destination points to make it cleaner) then we get the following image:
As you can see, the y-axis is flipped, if we invert the y-axis with:
source[:,1] = orig.shape[0] - source[:,1]
before plotting, then we get the following:
So that is the first problem (don't forget to invert the destination points as well), the second has to do with the transform itself:
t.estimate(destination,source,3)
From the documentation we see that the call takes the source points first, then the destination points. So the order of those arguments should be flipped.
Lastly, the clicked points are of the form (x,y), but the image is stored as (y,x), so we have to transpose the image before applying the transform and then transpose back again:
img_warped = transform.warp(orig.transpose(), t, order=2, mode='constant',cval=float('nan'))
img_warped = img_warped.transpose()
When you make these changes, you get the following warped image:
These lines aren't perfectly flat but it makes much more sense.
Thank you very much for the detailed answer! I cannot believe I did not see the axis inversion problem... Thanks for catching it!
But I am afraid your final solution does not solve my problem... The image you get is still crazy. It should be continuous, no have such big holes and weird distortions... (see final solution below)
I found I could get a reasonable solution using RANSAC:
from skimage.measure import ransac
t, inliers = ransac((destination,source), transform.PolynomialTransform, min_samples=20,residual_threshold=1.0, max_trials=1000)
outliers = inliers == False
I then get the following result
Note that I think I was right using (destination,source) in that order! I think it has to do with the fact that transform.warp requires the inverse_map as input for the transformation object, not the forward map. But maybe I am wrong? The good result I am getting suggest it's correct.
I guess that Polynomial transforms are too unstable, and using RANSAC allows to get a reasonable solution.
My problem was then to find a way to change the polynomial order in the RANSAC call...
transform.PolynomialTransform() does not take any parameters, and uses by default a 2nd order polynomial, but from the result I can see I would need a 3rd or 4th order polynomial.
So I opened a new question, and got a solution from Stefan van der Walt. Follow the link to see how to do it.
Thanks again for your help!
Consider the following code
import matplotlib.pyplot as plt
import numpy as np
time=np.arange(-100,100,01)
val =np.sin(time/10.)
time=-1.0*time
plt.figure()
plt.plot(time,val)
plt.xlim([70,-70])
plt.savefig('test.pdf')
when I open the pdf in inkscape, I can select (with F2) the entire data, it's just invisible outside of the specified xlim interval:
The problem seems to be the line
time=-1.0*time
If I omit this line, everything works perfectly.. no idea why this is. I often need such transformations because I deal with paleo-climate data which are sometimes given in year B.C. and year A.D., respectively.
The problem I see with this behavior is that someone could in principle get the data outside the range which I want to show.
Has someone a clue how to solve this problem (except for an slice of the arrays before plotting)?
I use matplotlib 1.1.1rc2
You can mask your array when plotting according to the limits you choose. Yes, this also requires changes to the code, but maybe not as extensive as you might fear. Here's an updated version of your example:
import matplotlib.pyplot as plt
import numpy as np
time=np.arange(-100,100,01)
val =np.sin(time/10.)
time=-1.0*time
plt.figure()
# store the x-limites in variables for easy multi-use
XMIN = -70.0
XMAX = 70.0
plt.plot(np.ma.masked_outside(time,XMIN,XMAX),val)
plt.xlim([XMIN,XMAX])
plt.savefig('test.pdf')
The key change is using np.ma.masked_outside for your x-axis value (note: the order of XMIN and XMAX in the mask-command is not important).
That way, you don't have to change the array time if you wanted to use other parts of it later on.
When I checked with inkscape, no data outside of the plot was highlighted.