matplotlib中的放大插图,无需重新绘制数据 [英] Zoomed inset in matplotlib without re-plotting data

查看:76
本文介绍了matplotlib中的放大插图,无需重新绘制数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理一些matplotlib图,并且需要放大插图.这可以通过 axes_grid1 工具包中的 zoomed_inset_axes 实现.请参阅示例http://matplotlib.org/1.3.1/_images/inset_locator_demo21.png

但正如您在代码中看到的,数据必须绘制两次 - 一次用于主轴(ax.imshow...)和一次用于插入轴(axins.imshow...).

我的问题是:

有没有办法在主图完成后添加放大的插图,无需在新轴上再次绘制所有内容?

请注意:我不是在寻找用函数包装绘图调用并让函数绘图 axaxins(参见下面的示例)的解决方案,但是(如果存在)本地解决方案,它利用 ax 中的现有数据.有人知道这样的解决方案是否存在吗?

这是包装解决方案:

def plot_with_zoom(*args, **kwargs):ax.imshow(* args,** kwargs)axins.imshow(*args, **kwargs)

它有效,但感觉有点像黑客,因为如果我只想放大现有绘图的一个区域,为什么还需要再次绘制所有数据.

<小时>

在 ed-smith 回答后的一些额外说明:

上面的示例当然只是最小的示例.情节中可能有许多不同的数据集(以及数据集是指通过 imshow plot 等进行绘制的事物).想象一下,例如一个散点图,有 10 个点数组,所有的点都是相对于公共 x 绘制的.

正如我上面写的,最直接的方法是使用一个包装器来绘制所有实例中的数据.但是我正在寻找的是一种从最终的 ax 对象(而不是各个绘图命令)开始的方法(如果存在的话),并以某种方式创建缩放的插图.

解决方案

我认为以下内容可以满足您的需求.请注意,您将返回的句柄用于第一个 imshow 并将其添加到轴以进行插入.您需要制作一个副本,以便每个图都有单独的句柄,

 将matplotlib.pyplot导入为plt从 mpl_toolkits.axes_grid1.inset_locator 导入 zoomed_inset_axes从 mpl_toolkits.axes_grid1.inset_locator 导入 mark_inset将numpy导入为np导入副本def get_demo_image():从matplotlib.cbook导入get_sample_data将numpy导入为npf = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)z = np.load(f)#z是一个15x15的numpy数组返回 z, (-3,4,-4,3)无花果,ax = plt.subplots(figsize = [5,4])#准备演示图像Z,范围 = get_demo_image()Z2 = np.zeros([150,150],dtype ="d")ny, nx = Z.shapeZ2[30:30+ny, 30:30+nx] = Z# 范围 = [-3, 4, -4, 3]即时通讯= ax.imshow(Z2,范围=范围,插值=最近",原点=下")#无复制,图片仅在插入时显示imcopy = copy.copy(im)axins = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6axins.add_artist(imcopy)原始图片的#个子区域x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9axins.set_xlim(x1, x2)axins.set_ylim(y1, y2)plt.xticks(visible = False)plt.yticks(可见=假)#在父轴中绘制插入轴区域的bbox,然后#bbox和插入轴区域之间的连接线mark_inset(ax,axins,loc1 = 2,loc2 = 4,fc ="none",ec ="0.5")plt.draw()plt.show()

对于您的包装函数,这将类似于,

def plot_with_zoom(*args, **kwargs):im = ax.imshow(*args, **kwargs)imcopy = copy.copy(im)axins.add_artist(imcopy)

但是,由于 imshow 仅将存储在数组 Z 中的数据显示为图像,所以我认为这种解决方案实际上要比对的两个单独调用慢即时展示.对于需要更多时间的图,例如轮廓图或pcolormesh ,这种方法可能是明智的...

超越单个 imshow,适用于多个不同类型的绘图.绘图函数都返回不同的句柄(例如 plot 返回行列表,imshow 返回 matplotlib.image.AxesImage 等).您可以在绘图时继续将这些句柄添加到列表(或字典)中(或使用 collection 如果它们足够相似).然后,您可以编写一个通用函数,该函数使用缩放轴上的add_artist或add_patch方法将它们添加到轴上,可能使用类型检查来处理绘图中使用的各种类型.一个更简单的方法可能是循环 ax.get_children() 并重用任何不是轴本身的元素.

另一种选择可能是研究划线技术,光栅化或其他使用的技术以加快动画处理,例如使用 fig.canvas.copy_from_bbox fig.canvas.tostring_rgb 将整个图形复制为图像(请参见buffer),读回并绘制放大版本.

I'm working on some matplotlib plots and need to have a zoomed inset. This is possible with the zoomed_inset_axes from the axes_grid1 toolkit. See the example here:

import matplotlib.pyplot as plt

from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset

import numpy as np

def get_demo_image():
    from matplotlib.cbook import get_sample_data
    import numpy as np
    f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
    z = np.load(f)
    # z is a numpy array of 15x15
    return z, (-3,4,-4,3)

fig, ax = plt.subplots(figsize=[5,4])

# prepare the demo image
Z, extent = get_demo_image()
Z2 = np.zeros([150, 150], dtype="d")
ny, nx = Z.shape
Z2[30:30+ny, 30:30+nx] = Z

# extent = [-3, 4, -4, 3]
ax.imshow(Z2, extent=extent, interpolation="nearest",
          origin="lower")

axins = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6
axins.imshow(Z2, extent=extent, interpolation="nearest",
             origin="lower")

# sub region of the original image
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)

plt.xticks(visible=False)
plt.yticks(visible=False)

# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")

plt.draw()
plt.show()

This will give the desired result:

http://matplotlib.org/1.3.1/_images/inset_locator_demo21.png

But as you can see in the code, the data has to be plotted twice - once for the main axis (ax.imshow...) and once for the inset axis (axins.imshow...).

My question is:

Is there a way to add a zoomed inset after the main plot is completed, without the need to plot everything again on the new axis?

Please note: I am not looking for a solution which wraps the plot call with a function and let the function plot ax and axins (see example below), but (if this exists) a native solution that makes use of the existing data in ax. Anybody knows if such a solution exists?

This is the wrapper-solution:

def plot_with_zoom(*args, **kwargs):
    ax.imshow(*args, **kwargs)
    axins.imshow(*args, **kwargs)

It works, but it feels a bit like a hack, since why should I need to plot all data again if I just want to zoom into a region of my existing plot.


Some additional clarification after the answer by ed-smith:

The example above is of course only the minimal example. There could be many different sets of data in the plot (and with sets of data I mean things plotted via imshow or plot etc). Imagine for example a scatter plot with 10 arrays of points, all plotted vs. common x.

As I wrote above, the most direct way to do that is just have a wrapper to plot the data in all instances. But what I'm looking for is a way (if it exists) to start with the final ax object (not the individual plotting commands) and somehow create the zoomed inset.

解决方案

I think the following does what you want. Note that you use the returned handle to the first imshow and add it to the axis for the insert. You need to make a copy so you have a separate handle for each figure,

import matplotlib.pyplot as plt

from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset

import numpy as np
import copy

def get_demo_image():
    from matplotlib.cbook import get_sample_data
    import numpy as np
    f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
    z = np.load(f)
    # z is a numpy array of 15x15
    return z, (-3,4,-4,3)

fig, ax = plt.subplots(figsize=[5,4])

# prepare the demo image
Z, extent = get_demo_image()
Z2 = np.zeros([150, 150], dtype="d")
ny, nx = Z.shape
Z2[30:30+ny, 30:30+nx] = Z

# extent = [-3, 4, -4, 3]
im = ax.imshow(Z2, extent=extent, interpolation="nearest",
          origin="lower")

#Without copy, image is shown in insert only
imcopy = copy.copy(im)
axins = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6
axins.add_artist(imcopy)

# sub region of the original image
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)

plt.xticks(visible=False)
plt.yticks(visible=False)

# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")

plt.draw()
plt.show()

For your wrapper function, this would be something like,

def plot_with_zoom(*args, **kwargs):
    im = ax.imshow(*args, **kwargs)
    imcopy = copy.copy(im)
    axins.add_artist(imcopy)

However, as imshow just displays the data stored in array Z as an image, I would think this solution would actually be slower than two separate calls to imshow. For plots which take more time, e.g. a contour plot or pcolormesh, this approach may be sensible...

EDIT:

Beyond a single imshow, and for multiple plots of different types. Plotting functions all return different handles (e.g. plot returns a list of lines, imshow returns a matplotlib.image.AxesImage, etc). You could keep adding these handles to a list (or dict) as you plot (or use a collection if they are similar enough). Then you could write a general function which adds them to an axis using add_artist or add_patch methods from the zoomed axis, probably with if type checking to deal with the various types used in the plot. A simpler method may be to loop over ax.get_children() and reuse anything which isn't an element of the axis itself.

Another option may be to look into blitting techniques, rasterization or other techniques used to speed up animation, for example using fig.canvas.copy_from_bbox or fig.canvas.tostring_rgb to copy the entire figure as an image (see why is plotting with Matplotlib so slow?‌​low). You could also draw the figure, save it to a non-vector graphic (with savefig or to a StringIO buffer), read back in and plot a zoomed in version.

这篇关于matplotlib中的放大插图,无需重新绘制数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆