I want to make an animation of multiple plots whose rendering evolves in time.
The files that I need are under the format, for example for one :
DD0043/DD0043. So I use the trick : f'{43:04}' to fill the zeros leading for each file (the files go from DD0000/DD0000 to DD0922/DD0922.
Here the script, warning, the plot is done with yt-project tool :
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
plot._switch_ds(array_data[i])
# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())
# Array for each data directory
array_data = np.array(numFiles)
for i in range(numFiles):
data = yt.load('DD'+str(f'{i:04}')+'/DD'+str(f'{i:04}'))
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = ds.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(ds, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(array_data[i].current_time.to('Gyr')))
fig = plt.figure()
animation = FuncAnimation(fig, animate, frames=numFiles)
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
But at the execution, I get the following error :
923
Traceback (most recent call last):
File "vol-annotated.py", line 52, in <module>
animation.save('animation.mp4')
File "/Users/fab/Library/Python/3.7/lib/python/site-packages/matplotlib/animation.py", line 1135, in save
anim._init_draw()
File "/Users/fab/Library/Python/3.7/lib/python/site-packages/matplotlib/animation.py", line 1743, in _init_draw
self._draw_frame(next(self.new_frame_seq()))
StopIteration
I don't know if I do the things correctly, especially for the variable fig that I initialize with :
fig = plt.figure()
Actually, I am trying to adapt to my case this script which creates a movie :
make animation
i.e :
import yt
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
ts = yt.load('GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*')
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)
fig = plot.plots['density'].figure
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
ds = ts[i]
plot._switch_ds(ds)
animation = FuncAnimation(fig, animate, frames=len(ts))
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
UPDATE 1: I didn't find a way to use animation.save correctly to generate an animation: always this issue about the fig variable.
But I managed to generate all the images corresponding for each one to an output file DDxxxx/DDxxxx. I have proceeded like this:
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())
# Loop to load input files
ts = []
for j in range(numFiles):
ts = np.append(ts, yt.load('DD'+str(f'{j:04}')+'/DD'+str(f'{j:04}')))
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)
# create plotting figure
fig = plot.plots['density'].figure
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
ds = ts[i]
sc = yt.create_scene(ds, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = ds.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(ds, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(ds.current_time.to('Gyr')))
## Here the scene needs to be painted into my figure / plot.
sc.save('rendering_'+str(i)+'.png')
animation = FuncAnimation(fig, animate, frames=numFiles)
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
If I open a single .png, I get a correct image representing a 3D scene.
Unfortunately, the animation function is not working, I get just a 2D heatmap plot showing the density projected: I would like to get an animation of the 3D scene figures (rendering_xxx.png).
It seems that I have to use ffmpeg to generate this animation from the multiple .png image, excepted if I find a way to know how to use Python FuncAnimation function (included in yt library ? or in Python by default ?).
UPDATE 2: here an example of figure (a frame actually) of animation I would like to get (this is a figure which represents gas density inside a box, i.e. in 3D) :
Unfortunately, #NightTrain's script produces this kind of plot :
As you can see, I don't understand why I get a 2D heatmap with NightTrain's solution instead of a 3D scene.
Moreover, there is no animation in this 2D heatmap, the movie displays always this same figure.
UPDATE3 : the last solution suggested by #Night train produces the following error :
Traceback (most recent call last):
File "plot_3D_enzo_with_animation_LAST.py", line 30, in <module>
plot = yt.SlicePlot(ts[0], 'z', 'density')
File "/Users/henry/Library/Python/3.7/lib/python/site-packages/yt/data_objects/time_series.py", line 201, in __getitem__
o = self._pre_outputs[key]
IndexError: list index out of range
I don't understand why this error occurs.
If you could provide more information it would be easier to help. I fixed your code and it is running now.
You also forgot to use the text_string variable.
Since the array_data variable isn't used I removed it.
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt
import pathlib
import glob
base_path = "enzo_tiny_cosmology"
paths = sorted(glob.glob(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]"))
# paths = [x.joinpath(x.name).as_posix() for x in sorted(pathlib.Path(base_path).glob("DD*"))]
# Array for each data directory
# array_data = np.zeros(len(paths))
# array_data = [None for x in range(len(paths))]
ts = yt.load(paths)
# ts = yt.load(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]")
# print(ts.outputs)
plot = yt.SlicePlot(ts[0], 'z', 'density')
fig = plot.plots['density'].figure
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
data = ts[i]
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = data.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(data, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(data.current_time.to('Gyr')))
plot._switch_ds(data)
animation = FuncAnimation(fig, animate, frames = len(paths))
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
Instead of counting the lines of ls -dlyou might want to use a python solution. which also lets you use the paths directly without contructing them later. You can use either pathlib or the os module.
import pathlib
import glob
base_path = "enzo_tiny_cosmology"
paths = sorted(glob.glob(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]"))
paths = [x.joinpath(x.name).as_posix() for x in sorted(pathlib.Path(base_path).glob("DD*"))]
For testing I downloaded these datasets:
curl -sSO https://yt-project.org/data/enzo_tiny_cosmology.tar.gz
tar xzf enzo_tiny_cosmology.tar.gz
curl -sSO https://yt-project.org/data/GasSloshingLowRes.tar.gz
tar xzf GasSloshingLowRes.tar.gz
UPDATE:
If you want to save the rendered scenes as video you could e.g. use imageio or opencv:
import yt, glob, imageio
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(data):
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = data.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(data, color=[1, 1, 1, .01])
plot._switch_ds(data)
sc.save(f'rendering_{i:04d}.png')
return sc.render()
paths = sorted(glob.glob("/DD*/DD[0-9][0-9][0-9][0-9]"))
ts = yt.load(paths)
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)
vid_writer = imageio.get_writer("animation.mp4", fps = 10)
for frame in ts:
rendered_image = animate(frame)
vid_writer.append_data(rendered_image)
vid_writer.close()
There are some issues that I can see right away.
The animate function refers to a plot variable that is not defined.
array_data = np.array(numFiles) will result in the number of files in a one-item numpy array. Probably not intended and will cause that array_data[i] fails for i>=1.
array_data is not filled with data afterwards, either.
I don't see any plotting being done. fig = plt.figure() will only provide you with an empty figure.
So, with that I'll restructure your code a bit:
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt
# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())
# create plotting figure
fig = plt.figure()
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
data = yt.load('DD'+str(f'{i:04}')+'/DD'+str(f'{i:04}'))
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = ds.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(ds, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(data.current_time.to('Gyr')))
## Here the scene needs to be painted into your figure / plot.
animation = FuncAnimation(fig, animate, frames=numFiles)
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
However, I also see in the example that yt supports loading several files at once:
ts = yt.load('GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*') so you might want to consider that as well.
I'm well aware that this is not a running example, but I hope this will help you tracking this down.
Related
In my project, I have many polygons to draw for each time step.
At each step, the number of polygons varies, thus it is difficult to keep Axes.patchs and translate them to make the animation.
I want to create animation with final figures (show after calling matplotlib.pyplot.show()), how to do this?
We take the sin curve as example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ims = []
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
z = np.cos(x)
for i in range(1,100):
tmpx = x[:i]
tmpy = y[:i]
tmpz = z[:i]
plt.plot(tmpx, tmpz)
im = plt.plot(tmpx, tmpy)
ims.append(im)
ani = animation.ArtistAnimation(fig, ims, interval=200)
ani.save('/home/test.gif', writer='imagemagick')
plt.show()
There are two curves: animated-sin-curve and static-cos-curve.
the sin-curve is kept as Line2D objects for each step
the cos-curve stay static for each step.
In this way, we show different Artist object for each step.
But I want to keep the rasterized Line2D figure for each step.
I find classes of AxesImage/FigureImage, but I don't know how to save the rasterized figure and make them work.
I tried to convert figure.canvas to AxesImage with following code :
def fig2AxesImage(fig):
import PIL.Image as Image
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
buf = numpy.fromstring(fig.canvas.tostring_argb(), dtype=numpy.uint8)
buf.shape = (w, h, 4)
# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = numpy.roll(buf, 3, axis=2)
image = Image.frombytes("RGBA", (w, h), buf.tostring())
image = numpy.asarray(image)
return plt.imshow(image, animated=True)
but with this way, I have to clear canvas at start of next frame, which make the final animation a blank video. (but the .jpg figures I output for each step get the right content)
Does anyone have done this before that save rasterized canvas-figures of matplotlib.pyplot.figure() as a animation Vedio?
celluloid for python 2.7
''' copy from celluloid'''
# from typing import Dict, List # not supported by python 2.7. So comment it
from collections import defaultdict
from matplotlib.figure import Figure
from matplotlib.artist import Artist
from matplotlib.animation import ArtistAnimation
__version__ = '0.2.0'
class Camera:
def __init__(self, figure):
self.figure_ = figure
self.offsets_ = { k:defaultdict(int) \
for k in ['collections', 'patches', 'lines', 'texts', 'artists', 'images']
}
self.photos_ = []
def snap(self):
frame_artists = []
for i, axis in enumerate(self.figure_.axes):
if axis.legend_ is not None:
axis.add_artist(axis.legend_)
for name in self.offsets_:
new_artists = getattr(axis, name)[self.offsets_[name][i]:]
frame_artists += new_artists
self.offsets_[name][i] += len(new_artists)
self.photos_.append(frame_artists)
def animate(self):
return ArtistAnimation(self.figure_, self.photos_)
The following code draws the cdf for datetime values:
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import numpy as np; np.random.seed(42)
import pandas as pd
objDate = dates.num2date(np.random.normal(735700, 300, 700))
ser = pd.Series(objDate)
ax = ser.hist(cumulative=True, density=1, bins=500, histtype='step')
plt.show()
How can I remove the vertical line at the right-most end of graph? The approach mentioned here doesn't work as replacing line#9 with:
ax = ser.hist(cumulative=True, density=1, bins=sorted(objDate)+[np.inf], histtype='step')
gives
TypeError: can't compare datetime.datetime to float
The CDF is actually drawn as a polygon, which in matplotlib is defined by a path. A path is in turn defined by vertices (where to go) and codes (how to get there). The docs say that we should not directly alter these attributes, but we can make a new polygon derived from the old one that suits our needs.
poly = ax.findobj(plt.Polygon)[0]
vertices = poly.get_path().vertices
# Keep everything above y == 0. You can define this mask however
# you need, if you want to be more careful in your selection.
keep = vertices[:, 1] > 0
# Construct new polygon from these "good" vertices
new_poly = plt.Polygon(vertices[keep], closed=False, fill=False,
edgecolor=poly.get_edgecolor(),
linewidth=poly.get_linewidth())
poly.set_visible(False)
ax.add_artist(new_poly)
plt.draw()
You should arrive at something like the figure below:
I am trying to pickle a matplotlib Figure object to be able to regenerate the graph with x and y data and labels and title at a later time. Is this possible?
When trying to use open and dump to pickle I get this traceback:
#3rd Party Imports and built-in
import random
import matplotlib.pyplot as plt
import pickle as pk
#Initializing lists for x and y values. Voltage trim and current measure is our x and y in this case.
voltage_trim = range(100, 150)
current_meas = []
# A change of parameters modelled by a multiplier in this case
multiplier = range(1,4)
# Initializing lists to store the output current if wanted
current_storage = []
# Required for Matplotlib
plt.close()
plt.ion() #Required method call in order for interactive plotting to work
# SPECIFY GRAPH
fig1 = plt.figure()
ax = fig1.add_subplot(1,1,1) # Creates an axis in order to have multiple lines
plt.title('Voltage Trim Vs Current \nsome fancy sub-title here')
plt.xlabel('Voltage Trim / V')
plt.ylabel('Current Measured/ A')
plt.grid(True)
color_choices = ['k', 'g','r','b','k','c', 'm', 'y'] # Add more according to number of graphs
# MAIN TEST LOOPS
for this_mult in multiplier:
current_meas = [] # Clears the output list to graph with different multipier
#Enumerates input in order to manipulate it below
for index, value in enumerate(voltage_trim):
#Generating random current values in this case
current_meas.append(random.randint(0,10)*this_mult)
print index ,'Generating results...'
print index, value
# Increments index so that lists match dimensiosn and graphing is possible
index += 1
# Optional real time plotting function, comment out if not wanted
live_plotting = ax.plot(voltage_trim[:index], current_meas, color = color_choices[this_mult])#,label = 'Line'+str(this_mult)
# A pyplot method that pauses the loop, updates the graph and continues to enable for real time graphing, set to small number to be considered insignificant
plt.pause(1e-124)
# Deletes the real time line to save memory in the loop
live_plotting[0].remove()
# This is the actual storage of plot objects, specify legend label here, and all other arguments the same
ax.plot(voltage_trim, current_meas,color = color_choices[this_mult],marker = 'o', label = 'Line'+str(this_mult))
#Stores the measured current (A)
current_storage.append(current_meas)
#Calls legend - must be in outer loop
plt.legend()
f = open('testt','wb')
pk.dump(fig1, f)
f.close()
Yes. Try
import pickle
import matplotlib.pyplot as plt
file = open('myfile', 'wb')
fig = plt.gcf()
pickle.dump(fig, file)
file.close()
Then to read
file = open('myfile', 'rb')
pickle.load(file)
plt.show()
file.close()
Short version: is there a Python method for displaying an image which shows, in real time, the pixel indices and intensities? So that as I move the cursor over the image, I have a continually updated display such as pixel[103,214] = 198 (for grayscale) or pixel[103,214] = (138,24,211) for rgb?
Long version:
Suppose I open a grayscale image saved as an ndarray im and display it with imshow from matplotlib:
im = plt.imread('image.png')
plt.imshow(im,cm.gray)
What I get is the image, and in the bottom right of the window frame, an interactive display of the pixel indices. Except that they're not quite, as the values are not integers: x=134.64 y=129.169 for example.
If I set the display with correct resolution:
plt.axis('equal')
the x and y values are still not integers.
The imshow method from the spectral package does a better job:
import spectral as spc
spc.imshow(im)
Then in the bottom right I now have pixel=[103,152] for example.
However, none of these methods also shows the pixel values. So I have two questions:
Can the imshow from matplotlib (and the imshow from scikit-image) be coerced into showing the correct (integer) pixel indices?
Can any of these methods be extended to show the pixel values as well?
There a couple of different ways to go about this.
You can monkey-patch ax.format_coord, similar to this official example. I'm going to use a slightly more "pythonic" approach here that doesn't rely on global variables. (Note that I'm assuming no extent kwarg was specified, similar to the matplotlib example. To be fully general, you need to do a touch more work.)
import numpy as np
import matplotlib.pyplot as plt
class Formatter(object):
def __init__(self, im):
self.im = im
def __call__(self, x, y):
z = self.im.get_array()[int(y), int(x)]
return 'x={:.01f}, y={:.01f}, z={:.01f}'.format(x, y, z)
data = np.random.random((10,10))
fig, ax = plt.subplots()
im = ax.imshow(data, interpolation='none')
ax.format_coord = Formatter(im)
plt.show()
Alternatively, just to plug one of my own projects, you can use mpldatacursor for this. If you specify hover=True, the box will pop up whenever you hover over an enabled artist. (By default it only pops up when clicked.) Note that mpldatacursor does handle the extent and origin kwargs to imshow correctly.
import numpy as np
import matplotlib.pyplot as plt
import mpldatacursor
data = np.random.random((10,10))
fig, ax = plt.subplots()
ax.imshow(data, interpolation='none')
mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'))
plt.show()
Also, I forgot to mention how to show the pixel indices. In the first example, it's just assuming that i, j = int(y), int(x). You can add those in place of x and y, if you'd prefer.
With mpldatacursor, you can specify them with a custom formatter. The i and j arguments are the correct pixel indices, regardless of the extent and origin of the image plotted.
For example (note the extent of the image vs. the i,j coordinates displayed):
import numpy as np
import matplotlib.pyplot as plt
import mpldatacursor
data = np.random.random((10,10))
fig, ax = plt.subplots()
ax.imshow(data, interpolation='none', extent=[0, 1.5*np.pi, 0, np.pi])
mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'),
formatter='i, j = {i}, {j}\nz = {z:.02g}'.format)
plt.show()
An absolute bare-bones "one-liner" to do this: (without relying on datacursor)
def val_shower(im):
return lambda x,y: '%dx%d = %d' % (x,y,im[int(y+.5),int(x+.5)])
plt.imshow(image)
plt.gca().format_coord = val_shower(ims)
It puts the image in closure so makes sure if you have multiple images each will display its own values.
All of the examples that I have seen only work if your x and y extents start from 0. Here is code that uses your image extents to find the z value.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
d = np.array([[i+j for i in range(-5, 6)] for j in range(-5, 6)])
im = ax.imshow(d)
im.set_extent((-5, 5, -5, 5))
def format_coord(x, y):
"""Format the x and y string display."""
imgs = ax.get_images()
if len(imgs) > 0:
for img in imgs:
try:
array = img.get_array()
extent = img.get_extent()
# Get the x and y index spacing
x_space = np.linspace(extent[0], extent[1], array.shape[1])
y_space = np.linspace(extent[3], extent[2], array.shape[0])
# Find the closest index
x_idx= (np.abs(x_space - x)).argmin()
y_idx= (np.abs(y_space - y)).argmin()
# Grab z
z = array[y_idx, x_idx]
return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, z)
except (TypeError, ValueError):
pass
return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, 0)
return 'x={:1.4f}, y={:1.4f}'.format(x, y)
# end format_coord
ax.format_coord = format_coord
If you are using PySide/PyQT here is an example to have a mouse hover tooltip for the data
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Mouse tooltip
from PySide import QtGui, QtCore
mouse_tooltip = QtGui.QLabel()
mouse_tooltip.setFrameShape(QtGui.QFrame.StyledPanel)
mouse_tooltip.setWindowFlags(QtCore.Qt.ToolTip)
mouse_tooltip.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
mouse_tooltip.show()
def show_tooltip(msg):
msg = msg.replace(', ', '\n')
mouse_tooltip.setText(msg)
pos = QtGui.QCursor.pos()
mouse_tooltip.move(pos.x()+20, pos.y()+15)
mouse_tooltip.adjustSize()
fig.canvas.toolbar.message.connect(show_tooltip)
# Show the plot
plt.show()
with Jupyter you can do so either with datacursor(myax)or by ax.format_coord.
Sample code:
%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
X = 10*np.random.rand(5,3)
fig,ax = plt.subplots()
myax = ax.imshow(X, cmap=cm.jet,interpolation='nearest')
ax.set_title('hover over the image')
datacursor(myax)
plt.show()
the datacursor(myax) can also be replaced with ax.format_coord = lambda x,y : "x=%g y=%g" % (x, y)
In case you, like me, work on Google Colab, this solutions do not work as Colab disabled interactive feature of images for matplotlib.
Then you might simply use Plotly:
https://plotly.com/python/imshow/
import plotly.express as px
import numpy as np
img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
[[0, 255, 0], [0, 0, 255], [255, 0, 0]]
], dtype=np.uint8)
fig = px.imshow(img_rgb)
fig.show()
Matplotlib has built-in interactive plot which logs pixel values at the corner of the screen.
To setup first install pip install ipympl
Then use either %matplotlib notebook or %matplotlib widget instead of %matplotlib inline
The drawback with plotly or Bokeh is that they don't work on Pycharm.
For more information take a look at the doc
To get interactive pixel information of an image use the module imagetoolbox
To download the module open the command prompt and write
pip install imagetoolbox
Write the given code to get interactive pixel information of an image
enter image description here
Output:enter image description here
I am creating two Python scripts to produce some plots for a technical report. In the first script I am defining functions that produce plots from raw data on my hard-disk. Each function produces one specific kind of plot that I need. The second script is more like a batch file which is supposed to loop around those functions and store the produced plots on my hard-disk.
What I need is a way to return a plot in Python. So basically I want to do this:
fig = some_function_that_returns_a_plot(args)
fig.savefig('plot_name')
But what I do not know is how to make a plot a variable that I can return. Is this possible? Is so, how?
You can define your plotting functions like
import numpy as np
import matplotlib.pyplot as plt
# an example graph type
def fig_barh(ylabels, xvalues, title=''):
# create a new figure
fig = plt.figure()
# plot to it
yvalues = 0.1 + np.arange(len(ylabels))
plt.barh(yvalues, xvalues, figure=fig)
yvalues += 0.4
plt.yticks(yvalues, ylabels, figure=fig)
if title:
plt.title(title, figure=fig)
# return it
return fig
then use them like
from matplotlib.backends.backend_pdf import PdfPages
def write_pdf(fname, figures):
doc = PdfPages(fname)
for fig in figures:
fig.savefig(doc, format='pdf')
doc.close()
def main():
a = fig_barh(['a','b','c'], [1, 2, 3], 'Test #1')
b = fig_barh(['x','y','z'], [5, 3, 1], 'Test #2')
write_pdf('test.pdf', [a, b])
if __name__=="__main__":
main()
If you don't want the picture to be displayed and only get a variable in return, then you can try the following (with some additional stuff to remove axis):
def myplot(t,x):
fig = Figure(figsize=(2,1), dpi=80)
canvas = FigureCanvasAgg(fig)
ax = fig.add_subplot()
ax.fill_between(t,x)
ax.autoscale(tight=True)
ax.axis('off')
canvas.draw()
buf = canvas.buffer_rgba()
X = np.asarray(buf)
return X
The returned variable X can be used with OpenCV for example and do a
cv2.imshow('',X)
These import must be included:
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg
The currently accepted answer didn't work for me as such, as I was using scipy.stats.probplot() to plot. I used matplotlib.pyplot.gca() to access an Axes instance directly instead:
"""
For my plotting ideas, see:
https://pythonfordatascience.org/independent-t-test-python/
For the dataset, see:
https://github.com/Opensourcefordatascience/Data-sets
"""
# Import modules.
from scipy import stats
import matplotlib.pyplot as plt
import pandas as pd
from tempfile import gettempdir
from os import path
from slugify import slugify
# Define plot func.
def get_plots(df):
# plt.figure(): Create a new P-P plot. If we're inside a loop, and want
# a new plot for every iteration, this is important!
plt.figure()
stats.probplot(diff, plot=plt)
plt.title('Sepal Width P-P Plot')
pp_p = plt.gca() # Assign an Axes instance of the plot.
# Plot histogram. This uses pandas.DataFrame.plot(), which returns
# an instance of the Axes directly.
hist_p = df.plot(kind = 'hist', title = 'Sepal Width Histogram Plot',
figure=plt.figure()) # Create a new plot again.
return pp_p, hist_p
# Import raw data.
df = pd.read_csv('https://raw.githubusercontent.com/'
'Opensourcefordatascience/Data-sets/master//Iris_Data.csv')
# Subset the dataset.
setosa = df[(df['species'] == 'Iris-setosa')]
setosa.reset_index(inplace= True)
versicolor = df[(df['species'] == 'Iris-versicolor')]
versicolor.reset_index(inplace= True)
# Calculate a variable for analysis.
diff = setosa['sepal_width'] - versicolor['sepal_width']
# Create plots, save each of them to a temp file, and show them afterwards.
# As they're just Axes instances, we need to call get_figure() at first.
for plot in get_plots(diff):
outfn = path.join(gettempdir(), slugify(plot.title.get_text()) + '.png')
print('Saving a plot to "' + outfn + '".')
plot.get_figure().savefig(outfn)
plot.get_figure().show()