在 Jupyter 中使用 matplotlib.Slider 侦听器更新颜色条时出现问题

问题描述 投票:0回答:1

我遇到的问题是在更新

plt.imshow
图的颜色条期间。这是代码,我将尝试将其分解并解释其中的一些内容。

  • 对于 Jupyter 中的第一个单元格,我有函数、导入和输入参数:
# Imports ##############################################################
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from matplotlib.widgets import Slider
from scipy.ndimage import label, find_objects
# Functions ############################################################
def intensity_distribution(r, z, PMax, w0, zR):
    wZ = w0 * np.sqrt(1 + (z / zR)**2)  # beam radius at z (Gaussian beam spreading)
    I0 = 2 * PMax / (np.pi * wZ**2) # peak intensity at radius wZ
    return I0 * np.exp(-2 * r**2 / wZ**2), wZ  # Gaussian intensity distribution, beam radius
def get_circle_ROI(I, r, threshold):
    ROI = (I > threshold).astype("uint8")  # binary mask for regions above the threshold
    labels, features = label(ROI) # label connected regions
    slices = find_objects(labels) # get bounding box slices for labeled regions
    xSlice, ySlice = slices[0] # extract x and y slices of the largest feature
    ROIHeight = (xSlice.stop - xSlice.start) * (r[1] - r[0]) * 1e6 # convert height to micrometers
    ROIWidth = (ySlice.stop - ySlice.start) * (r[1] - r[0]) * 1e6 # convert width to micrometers
    cx = (ySlice.start + ySlice.stop) // 2 # x-coordinate of the center
    cy = (xSlice.start + xSlice.stop) // 2 # y-coordinate of the center
    centre = (r[cy] * 1e6, r[cx] * 1e6) # convert center coordinates to micrometers
    radius = min(ROIWidth, ROIHeight) / 2 # radius is the smaller dimension's half-width
    return centre, radius
def update_plot(PMax, zOffset):
    """Update the heatmap based on new parameters."""
    global colorbar
    # Calculate intensity distribution at given z offset
    I, wZ = intensity_distribution(np.sqrt(R**2 + Z**2), zOffset, PMax, BEAM_RADIUS_AT_FOCUS, zR)
    I /= 1e6  # convert intensity from W/m² to W/mm²
    I += 0.01  # small offset for better visualization contrast
    max_intensity = I.max()  # maximum intensity in the current distribution
    # Calculate the on-axis peak intensity at focus in W/mm²
    I0 = (2 * PMax) / (np.pi * BEAM_RADIUS_AT_FOCUS**2)  # peak intensity in W/m² at z = 0
    I0 /= 1e6  # convert peak intensity to W/mm²
    # Calculate the Full Width at Half Maximum (FWHM) in micrometers
    centre, fwhm = get_circle_ROI(I, r, max_intensity / 2)
    _, tenth = get_circle_ROI(I, r, max_intensity / 10)
    # Clear and update plot
    ax.clear()  # clear current axes
    # Display the updated intensity distribution as a heatmap
    im = ax.imshow(I, extent=[r[0]*1e6, r[-1]*1e6, r[0]*1e6, r[-1]*1e6], norm=colors.LogNorm(vmax=14000))
    ax.set_xlabel("x (μm)")  # label for x-axis
    ax.set_ylabel("y (μm)")  # label for y-axis
    # Add plot title with z offset, FWHM, and max intensity in W/mm²
    ax.set_title(f"FWHM = {fwhm:.1f} μm\n"
                 f"Radius at 10% of total power = {tenth:.2f} μm\n"
                 f"Max power = {I.max():.2f} W/mm²",
                 loc="left")
    # Draw a circle representing the FWHM boundary
    cirlcefwhm = plt.Circle(centre, fwhm, color='white', fill=False, linestyle='--', linewidth=2, label="FWHM")
    cirlce10 = plt.Circle(centre, tenth, color='white', fill=False, linestyle='--', linewidth=2, label="10% of I$_max$")
    ax.add_patch(cirlcefwhm)  # add the FWHM circle to the plot
    ax.add_patch(cirlce10)  # add the circle where power is 10% of max
    #### Problematic starts here ####
    if colorbar is not None:   # if colorbar already exists, remove it
        colorbar.remove()
    colorbar = plt.colorbar(im, ax=ax, label="Intensity (W/mm²)")  # create new colorbar in W/mm²
    fig.draw_without_rendering() # redraw based on the recommendation of matplotlib instead of colorbar.draw_all()
    fig.canvas.draw()  # redraw figure to reflect updates
    #### Problematic ends here ####
def sliders_on_changed(val):
    ''' Slider update function '''
    power = power_slider.val * MAX_LASER_POWER / 100  # calculate current power level in watts
    z_offset = z_offset_slider.val / 1000 # convert slider z offset from mm to meters
    update_plot(power, z_offset) # update the plot with new parameters
# Inputs ###############################################################
WAVELENGTH = 10.6e-6  # wavelength in meters
MAX_LASER_POWER = 80  # max laser power in watts
BEAM_WIDTH_AT_FOCUS = 120e-6  # beam width at focus in meters
BEAM_RADIUS_AT_FOCUS = BEAM_WIDTH_AT_FOCUS / 2  # beam radius at focus in meters
zR = np.pi * BEAM_RADIUS_AT_FOCUS**2 / WAVELENGTH  # Rayleigh range in meters
gridSize = 100 # resolution
r = np.linspace(-500e-6, 500e-6, gridSize)  # range for spatial coordinates in meters
R, Z = np.meshgrid(r, r) # create grid for spatial coordinates
colorbar = None # init colorbar

有问题的部分是这个:

#### Problematic starts here ####
if colorbar is not None:   # if colorbar already exists, remove it
    colorbar.remove()
colorbar = plt.colorbar(im, ax=ax, label="Intensity (W/mm²)")  # create new colorbar in W/mm²
fig.draw_without_rendering() # redraw based on the recommendation of matplotlib instead of colorbar.draw_all()
fig.canvas.draw()  # redraw figure to reflect updates
#### Problematic ends here ####

我不太确定为什么它不起作用。发生的情况是,颜色条在第一次更新时出现,然后在 Z 偏移或功率百分比发生变化后消失。基本上在任何值发生变化之后。

  • jupyter Notebook 的第二个单元格调用以下函数:
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.35)  # leave space for sliders
update_plot(50, 0)
ax_power = plt.axes([0.25, 0.2, 0.65, 0.03], facecolor = "lightgray")
ax_z_offset = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor = "lightgray")
power_slider = Slider(ax_power, 'Power (%)', 0.1, 100, valinit=50)
z_offset_slider = Slider(ax_z_offset, 'Z-Offset (mm)', 0, 5.0, valinit=0)
power_slider.on_changed(sliders_on_changed)
z_offset_slider.on_changed(sliders_on_changed)

运行此命令将使您看到如下所示的用户界面:

First update

然后当我移动任何滑块时,颜色条就会消失:

After moving slider

颜色条的更新似乎是一个常见问题,我看到了很多关于它的stackoverflow问题,我什至回答过一次。

然而,该解决方案并不能真正与滑块侦听器一起使用。

P.S.:更改后端并没有真正的帮助。然而,使用

%matplotlib qt
显示了回溯:

Traceback (most recent call last):
  File "C:\ProgramData\anaconda3\Lib\site-packages\matplotlib\cbook\__init__.py", line 309, in process
    func(*args, **kwargs)
  File "C:\ProgramData\anaconda3\Lib\site-packages\matplotlib\widgets.py", line 603, in <lambda>
    return self._observers.connect('changed', lambda val: func(val))
                                                          ^^^^^^^^^
  File "C:\Users\User\AppData\Local\Temp\ipykernel_26252\2170461726.py", line 66, in sliders_on_changed
    update_plot(power, z_offset) # update the plot with new parameters
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\User\AppData\Local\Temp\ipykernel_26252\2170461726.py", line 57, in update_plot
    colorbar.remove()
  File "C:\ProgramData\anaconda3\Lib\site-packages\matplotlib\colorbar.py", line 1041, in remove
    self.ax.remove()
  File "C:\ProgramData\anaconda3\Lib\site-packages\matplotlib\artist.py", line 242, in remove
    self._remove_method(self)
  File "C:\ProgramData\anaconda3\Lib\site-packages\matplotlib\figure.py", line 944, in delaxes
    self._axstack.remove(ax)
  File "C:\ProgramData\anaconda3\Lib\site-packages\matplotlib\figure.py", line 92, in remove
    self._axes.pop(a)
KeyError: <Axes: label='<colorbar>', ylabel='Intensity (W/mm²)'>

我尝试过的一些东西:

  • 有一种
    colorbar
    的方法称为
    draw_all()
  • Matplotlib 还建议使用
    fig.draw_without_rendering()
  • 我自己的解决方案
  • 设置
    colorbar
    的限制而不删除颜色条

有人可以帮我找出问题所在吗?据我了解ofc代码很长,如果有必要我可以将其缩短为一个简单的示例。


我正在使用

  • Jupyter 6.5.4
  • Matplotlib 3.7.2
python matplotlib widget colorbar
1个回答
0
投票

我设法通过使用以下几行更新颜色图在旧版本上解决了这个问题:

if colorbar is not None: # if colorbar already exists, update it
    colorbar.mappable.set_clim(vmax=max_intensity)
else:    
    colorbar = plt.colorbar(im, ax=ax, label="Intensity (W/mm²)") # create new colorbar in W/mm²

在这种情况下不需要更新 Anaconda。上面的代码适用于旧版本和旧版本(在

%matplotlib qt
后端下)

证明:

Proof


有关使用

ipympl
的建议会在全新的 anaconda 安装下导致运行时错误。所以,真的没有必要。


使用

interact()
并不能解决任何问题。

© www.soinside.com 2019 - 2024. All rights reserved.