努力使用 ipywidgets 为图形调用多个交互函数

struggling in calling multiple interactive functions for a graph using ipywidgets

我希望有一个主图像,我可以在主图像上绘制螺旋线、椭圆形等,并使用可以改变强加绘图形状的变量。主图也需要有对比度变量。

我的代码目前看起来像这样;




###############################################BASIC FIGURE PLOT####################################
plt.figure(figsize=(24,24))
@interact
def spiral(Spiral=False,n=2000,x1=50,y1=50,z1=50,k1=300):
    if Spiral == False:
        x = 0;
        y = 0;
        plt.scatter(x,y,s = 3, c = 'black');
    else:
        angle = np.linspace(x1,y1*1*np.pi, n)
        radius = np.linspace(z1,k1,n)
        x = radius * np.cos(angle) + 150
        y = radius * np.sin(angle) + 150
        plt.scatter(x,y,s = 3, c = 'black');
@interact        
def contrast(vuc=(0.2,1,0.01),vlc=(0.1,1,0.01)):  
    vu = np.quantile(qphi, vuc);
    vl = np.quantile(qphi, vlc);
    print("upper =",vu, " lower=",vl);


plt.imshow(qphi, origin='lower',vmin=vl,vmax=vu);
plt.show() 

这会产生两个图; visible here 一个创建螺旋的图我可以自由编辑,另一个图是具有可变对比度的主图像。

任何关于如何结合这两个图的建议将不胜感激;谢谢!

有几种方法可以使用 ipywidgets 来控制 matplotlib 图。下面我使用每个选项创建了我认为您正在寻找的输出。这些方法按照发现的自然顺序列出,但是,我建议按以下顺序尝试它们:4, 2, 1, 3

方法 1 - 内联后端

如果您使用 %matplotlib inline,那么 matplotlib 图形将无法交互,您每次都需要重新创建整个绘图

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact

# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)


@interact
def graph(
    Spiral=True,
    n=2000,
    x1=50,
    y1=50,
    z1=50,
    k1=300,
    vlc=(0.1, 1, 0.01),
    vuc=(0.1, 1, 0.01),
):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
    if Spiral == False:
        x = 0
        y = 0
    else:
        angle = np.linspace(x1, y1 * 1 * np.pi, n)
        radius = np.linspace(z1, k1, n)
        x = radius * np.cos(angle) + 150
        y = radius * np.sin(angle) + 150
    ax1.scatter(x, y, s=3, color="k")
    vu = np.quantile(img, vuc)
    vl = np.quantile(img, vlc)
    ax2.imshow(img, vmin=vl, vmax=vu)

方法 2 - 交互式后端 + cla

您可以使用交互式 maptlotlib 后端之一来避免每次更改时都必须完全重新生成图形。要做到这一点,第一种方法是在每次使用 cla 方法更改滑块时简单地清除轴。

这将适用于 %matplotlib notebook%matplotlib ipympl。前者仅适用于 jupyter notebook,后者适用于 jupyter notebook 和 juptyerlab。 (这里是 ipympl 的安装信息:https://github.com/matplotlib/ipympl#installation

%matplotlib ipympl

import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, interactive, interactive_output

# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)


fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))


@interact
def graph(
    Spiral=True,
    n=2000,
    x1=50,
    y1=50,
    z1=50,
    k1=300,
    vlc=(0.1, 1, 0.01),
    vuc=(0.1, 1, 0.01),
):
    ax1.cla()
    ax2.cla()
    if Spiral == False:
        x = 0
        y = 0
    else:
        angle = np.linspace(x1, y1 * 1 * np.pi, n)
        radius = np.linspace(z1, k1, n)
        x = radius * np.cos(angle) + 150
        y = radius * np.sin(angle) + 150
    ax1.scatter(x, y, s=3, color="k")
    vu = np.quantile(img, vuc)
    vl = np.quantile(img, vlc)
    ax2.imshow(img, vmin=vl, vmax=vu)

方法 3 - 交互式后端 + set_data

当您绘制较大的数据集或您希望从一个交互到下一个交互中保留绘图的某些部分时,完全清除坐标轴可能效率低下。因此,您可以改为使用 set_dataset_offsets 方法来更新您已经绘制的内容。

%matplotlib ipympl

import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, interactive, interactive_output

# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)


fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
scat = ax1.scatter([0]*2000,[0]*2000,s=3, color='k')
im = ax2.imshow(img)

out = widgets.Output()
display(out)

@interact
def graph(
    Spiral=True,
    n=2000,
    x1=50,
    y1=50,
    z1=50,
    k1=300,
    vlc=(0.1, 1, 0.01),
    vuc=(0.1, 1, 0.01),
):
    if Spiral == False:
        x = 0
        y = 0
    else:
        angle = np.linspace(x1, y1 * 1 * np.pi, n)
        radius = np.linspace(z1, k1, n)
        x = radius * np.cos(angle) + 150
        y = radius * np.sin(angle) + 150
    scat.set_offsets(np.c_[x, y])
    # correctly scale the x and y limits
    ax1.dataLim = scat.get_datalim(ax1.transData)
    ax1.autoscale_view()

    vu = np.quantile(img, vuc)
    vl = np.quantile(img, vlc)
    im.norm.vmin = vl
    im.norm.vmax = vu

方法 4 - mpl_interactions

使用 set_offsets 和等效的 set_data 将是最高效的解决方案,但也很难弄清楚如何让它工作,甚至更难记住。为了方便起见,我创建了一个库 (mpl-interactions),它可以自动执行方法 3 的样板文件。

除了简单和高效之外,这还有一个优点,即您无需负责更新绘图,只需 return 获取正确的值。然后有一个附带的好处,现在像 spiral 这样的功能可以在代码的其他部分使用,因为它们只是 return 值而不是处理绘图。

另一个优点是 mpl-interactions 还可以创建 matplotlib 小部件,因此这是唯一也可以在笔记本之外使用的方法。

%matplotlib ipympl
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import mpl_interactions.ipyplot as iplt

img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)

# define the functions to be plotted
def spiral(Spiral=False, n=2000, x1=50, y1=50, z1=50, k1=300):
    if Spiral == False:
        x = 0
        y = 0
        return x, y
    else:
        angle = np.linspace(x1, y1 * 1 * np.pi, n)
        radius = np.linspace(z1, k1, n)
        x = radius * np.cos(angle) + 150
        y = radius * np.sin(angle) + 150
        return x, y


def vmin(vuc, vlc):
    return np.quantile(img, vlc)

def vmax(vlc, vuc):
    return np.quantile(img, vuc)


fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
controls = iplt.scatter(
    spiral,
    Spiral={(True, False)},
    n=np.arange(1800, 2200),
    x1=(25, 75),
    y1=(25, 75),
    z1=(25, 75),
    k1=(200, 400),
    parametric=True,
    s=3,
    c="black",
    ax=ax1,
)
controls = iplt.imshow(
    img,
    vmin=vmin,
    vmax=vmax,
    vuc=(0.1, 1, 1000),
    vlc=(0.1, 1, 1000),
    controls=controls[None],
    ax=ax2,
)