当前位置:首页>python>Python 绘制时间—频率—能量瀑布图(含代码)

Python 绘制时间—频率—能量瀑布图(含代码)

  • 2026-09-08 21:24:39
Python 绘制时间—频率—能量瀑布图(含代码)
案例代码见文末,感谢您关注PFC小姐姐,麻烦您多多对推文点赞、收藏及转发,并衷心希望您多多指教🙏,帮助PFC小姐姐进步提升。

引言

在处理信号分析、频谱演化、振动响应、声发射过程或其他时变频率数据时,常规二维图往往只能展示其中一个侧面:要么强调频率随时间的变化,要么强调某一时刻的能量分布,却很难在同一幅图中同时呈现“时间—频率—能量”三者之间的耦合关系。瀑布图的优势就在于,它能够把连续时刻的频谱切片沿时间方向依次展开,使能量随频率和时间的演化过程以更加直观的三维形式表现出来。这样得到的图,不仅能够观察主频带的迁移、能量峰值的出现位置和增强衰减过程,还可以更方便地识别不同频段之间的交替主导关系。下面这组示例图基于同一份模拟数据,分别展示基础版与进阶版两种时间—频率—能量瀑布图的绘制方式,用来说明如何在保证图面清晰的前提下,逐步增强单图的信息表达能力。

基础版时间—频率—能量瀑布图

下面这张图主要用于展示瀑布图最核心的结构。图中横坐标表示频率,纵坐标表示时间,竖向高度表示能量强弱。每一条沿频率方向展开的曲线,都对应一个时间切片下的能量分布,而所有时间切片依次排列后,就形成了完整的三维瀑布形态。通过这张图,可以直接观察不同频段能量随时间的起伏变化,例如某些低频带在前期更强,而某些中高频带在后期逐渐增强,局部强峰也会在三维空间中表现为更加突出的能量脊。这一版本的重点在于先把时间、频率和能量三者的基本关系清晰地建立起来,使读者能够从整体上理解时变频谱的演化过程。

进阶版时间—频率—能量瀑布图

在基础版的基础上进一步强化了重点信息的表达。与第一幅图相比,这一版本适当减少了普通切片线的数量,使整体结构更加简洁,同时对能量较强的关键切片进行了突出显示,从而使主要能量带的位置更加醒目。图中的青色主能量脊线表示在每一个时刻上能量峰值所在的主导频率,它将离散时间切片中的局部峰值串联起来,更直观地揭示了主频随时间迁移的连续轨迹。这样的表达方式不仅保留了瀑布图原有的三维层次感,还进一步强调了主导能量通道的演化规律,因此更适合用于展示复杂时频数据中的主控结构、能量集中区以及频率漂移特征。

    具体Python如下:

    import osimport numpy as npimport matplotlib.pyplot as pltfrom matplotlib import cmfrom matplotlib.colors import Normalize# 0. 输出目录OUTDIR = "waterfall_demo_output_clean"os.makedirs(OUTDIR, exist_ok=True)# 1. 构造模拟的时间-频率-能量数据def gaussian(x, mu, sigma):    return np.exp(-0.5 * ((x - mu) / sigma) ** 2)def moving_average_1d(x, w=5):    if w <= 1:        return x.copy()    kernel = np.ones(w) / w    return np.convolve(x, kernel, mode="same")def generate_tf_energy(nt=90, nf=260, seed=2026):    rng = np.random.default_rng(seed)    t = np.linspace(0, 40, nt)          # time    f = np.linspace(0, 120, nf)         # frequency    E = np.zeros((nt, nf), dtype=float)    for i, ti in enumerate(t):        # 成分1:低频起伏带        c1 = 18 + 10 * np.sin(2 * np.pi * ti / 18.0)        a1 = 1.2 + 0.5 * np.cos(2 * np.pi * ti / 11.0)        band1 = a1 * gaussian(f, c1, 4.5)        # 成分2:中高频摆动带        c2 = 68 + 13 * np.cos(2 * np.pi * (ti - 5) / 15.0)        a2 = 1.0 + 0.4 * np.sin(2 * np.pi * ti / 9.5)        band2 = a2 * gaussian(f, c2, 6.5)        # 成分3:斜向 chirp 型能量脊        c3 = 10 + 1.7 * ti        a3 = 0.8 + 0.25 * np.sin(2 * np.pi * ti / 8.0)        band3 = a3 * gaussian(f, c3, 3.0)        # 局部强脉冲热点        burst = (            1.8 * gaussian(ti, 10.0, 1.2) * gaussian(f, 88, 5.0) +            2.2 * gaussian(ti, 21.0, 1.5) * gaussian(f, 52, 4.0) +            1.6 * gaussian(ti, 31.0, 1.0) * gaussian(f, 97, 3.8)        )        # 背景起伏        background = (            0.15            + 0.10 * np.sin(0.15 * f + 0.55 * ti)            + 0.06 * np.cos(0.10 * f - 0.35 * ti)        )        # 噪声        noise = 0.05 * rng.normal(size=nf)        E[i, :] = band1 + band2 + band3 + burst + background + noise    E = np.clip(E, 0, None)    # 沿时间方向平滑    for j in range(nf):        E[:, j] = moving_average_1d(E[:, j], w=5)    return t, f, E# 2. 基础版瀑布图def plot_basic_waterfall(t, f, E, out_png):    fig = plt.figure(figsize=(12, 8), dpi=180)    ax = fig.add_subplot(111, projection="3d")    idx = np.arange(0, len(t), 2)    norm = Normalize(vmin=E.min(), vmax=E.max())    cmap = cm.viridis    for i in idx:        color = cmap(norm(E[i].max()))        ax.plot(            f,            np.full_like(f, t[i]),            E[i],            color=color,            lw=1.4,            alpha=0.95        )        ax.plot(            f,            np.full_like(f, t[i]),            np.zeros_like(f),            color=color,            lw=0.6,            alpha=0.12        )    ax.set_xlabel("Frequency", fontsize=12, labelpad=10)    ax.set_ylabel("Time", fontsize=12, labelpad=10)    ax.set_zlabel("Energy", fontsize=12, labelpad=8)    ax.view_init(elev=28, azim=-62)    ax.set_xlim(f.min(), f.max())    ax.set_ylim(t.min(), t.max())    ax.set_zlim(0, E.max() * 1.10)    ax.xaxis.pane.set_alpha(0.06)    ax.yaxis.pane.set_alpha(0.06)    ax.zaxis.pane.set_alpha(0.02)    ax.grid(True, alpha=0.25)    mappable = cm.ScalarMappable(norm=norm, cmap=cmap)    mappable.set_array([])    cbar = plt.colorbar(mappable, ax=ax, pad=0.08, shrink=0.78)    cbar.set_label("Slice peak energy", fontsize=11)    plt.tight_layout()    plt.savefig(out_png, bbox_inches="tight")# 3. 重新整理后的进阶版瀑布图def plot_advanced_waterfall(t, f, E, out_png):    fig = plt.figure(figsize=(12, 8), dpi=180)    ax = fig.add_subplot(111, projection="3d")    # 只抽取较少切片,避免太密    idx = np.arange(0, len(t), 4)    norm = Normalize(vmin=E.min(), vmax=E.max())    cmap = cm.plasma    peak_each_time = E.max(axis=1)    top_idx = np.argsort(peak_each_time)[-3:]   # 最强3个切片高亮    for i in idx:        color = cmap(norm(E[i].max()))        lw = 1.0        alpha = 0.45        if i in top_idx:            lw = 2.2            alpha = 0.95        ax.plot(            f,            np.full_like(f, t[i]),            E[i],            color=color,            lw=lw,            alpha=alpha        )    # 主能量脊线:每个时刻最大能量对应的频率    ridge_f = f[np.argmax(E, axis=1)]    ridge_z = E.max(axis=1)    ridge_f_smooth = moving_average_1d(ridge_f, w=7)    ridge_z_smooth = moving_average_1d(ridge_z, w=7)    # 主脊线本体    ax.plot(        ridge_f_smooth,        t,        ridge_z_smooth + 0.03 * E.max(),        color="cyan",        lw=2.4,        alpha=0.95,        label="Dominant energy ridge"    )    # 加少量散点,让这条线更有 3D 路径感    step = 6    ax.scatter(        ridge_f_smooth[::step],        t[::step],        ridge_z_smooth[::step] + 0.03 * E.max(),        s=18,        color="cyan",        edgecolors="black",        linewidths=0.4,        alpha=0.95    )    # 只标出一个全局峰值,不再写 Peak1 Peak2    g_idx = np.argmax(peak_each_time)    g_f = f[np.argmax(E[g_idx])]    g_z = E[g_idx].max()    ax.scatter(        [g_f], [t[g_idx]], [g_z],        s=52,        color="yellow",        edgecolors="black",        linewidths=0.8,        zorder=10    )    ax.set_xlabel("Frequency", fontsize=12, labelpad=10)    ax.set_ylabel("Time", fontsize=12, labelpad=10)    ax.set_zlabel("Energy", fontsize=12, labelpad=8)    ax.set_xlim(f.min(), f.max())    ax.set_ylim(t.min(), t.max())    ax.set_zlim(0, E.max() * 1.10)    # 视角略调整    ax.view_init(elev=27, azim=-58)    ax.xaxis.pane.set_alpha(0.05)    ax.yaxis.pane.set_alpha(0.05)    ax.zaxis.pane.set_alpha(0.02)    ax.grid(True, alpha=0.18)    mappable = cm.ScalarMappable(norm=norm, cmap=cmap)    mappable.set_array([])    cbar = plt.colorbar(mappable, ax=ax, pad=0.08, shrink=0.78)    cbar.set_label("Slice peak energy", fontsize=11)    ax.legend(loc="upper right", frameon=True, fontsize=10)    plt.tight_layout()    plt.savefig(out_png, bbox_inches="tight")# 4. 主程序def main():    t, f, E = generate_tf_energy()    basic_png = os.path.join(OUTDIR, "waterfall_basic.png")    adv_png = os.path.join(OUTDIR, "waterfall_advanced_clean.png")    plot_basic_waterfall(t, f, E, basic_png)    plot_advanced_waterfall(t, f, E, adv_png)if __name__ == "__main__":    main()

    特别声明:

    以上代码与文案均为网上资料整合而成,仅供广大同行们参考学习,如有侵权请联系删除。

    如有其他需要,欢迎关注我的咸鱼号:pfc小姐姐

    最新文章

    随机文章