@@ -36,6 +36,7 @@ def plot_IMFs(
3636 save_name : Optional [str ] = None ,
3737 fs : Optional [float ] = None ,
3838 freq_max : Optional [float ] = None ,
39+ show_alignment : bool = False ,
3940) -> Optional [plt .Figure ]:
4041 """
4142 Visualizes the numpy array of intrinsic mode functions derived from the decomposition of a signal.
@@ -60,6 +61,7 @@ def plot_IMFs(
6061 :param save_name: The name of the saved image file
6162 :param fs: sampling frequency for ``view="2d_freq"``
6263 :param freq_max: spectrum x-axis upper limit for ``view="2d_freq"``
64+ :param show_alignment: for multivariate 2D plots, append an Align column
6365 :return: The figure object for the plot
6466 """
6567 # Get the selected visualization
@@ -97,6 +99,7 @@ def plot_IMFs(
9799 dpi = dpi ,
98100 spine_width = spine_width ,
99101 save_name = save_name ,
102+ show_alignment = show_alignment ,
100103 )
101104 else :
102105 # The output data is in the wrong format
@@ -516,58 +519,105 @@ def plot_multi_IMFs(
516519 IMFs : np .ndarray ,
517520 max_imfs : Optional [int ] = - 1 ,
518521 colors : Optional [List ] = None ,
522+ channel_colors : Optional [List ] = None ,
519523 save_figure : Optional [bool ] = False ,
520524 return_figure : Optional [bool ] = False ,
521525 dpi : Optional [int ] = 64 ,
522526 fontsize : float = 8 ,
523527 spine_width : float = 2 ,
524528 save_name : Optional [str ] = None ,
529+ show_alignment : bool = False ,
525530) -> Optional [plt .Figure ]:
526531 """
527- Plotting a multivariate signal and its decomposed intrinsic mode functions
528- :param signal: The input original signal
529- :param IMFs: The intrinsic mode functions obtained after signal decomposition
532+ Plotting a multivariate signal and its decomposed intrinsic mode functions.
533+
534+ Layout (when ``show_alignment=True``)::
535+
536+ | Var-0 | Var-1 | ... | Align |
537+ |-------|-------|-----|-------|
538+ | Signal channel panels ... | all channels overlaid |
539+ | IMF-k channel panels ... | all channels of IMF-k |
540+
541+ The rightmost **Align** column overlays every channel of the same mode
542+ (paper-style mode-alignment check used in MVMD demos).
543+
544+ :param signal: The input original signal, shape ``(n_vars, seq_len)``
545+ :param IMFs: Modes of shape ``(n_imfs, seq_len, n_vars)``
530546 :param max_imfs: The number of decomposition modes to be plotted
531- :param colors: List of color strings for plotting
547+ :param colors: Row colors for per-channel panels (signal / IMF rows)
548+ :param channel_colors: Colors used in the Align column (one per channel)
532549 :param save_figure: Whether to save the figure as an image
533550 :param return_figure: Whether to return the figure object
534551 :param dpi: The resolution of the saved image
535552 :param fontsize: The font size of the axis labels
536553 :param save_name: The name of the saved image file
537554 :param spine_width: The width of the visible axes spines
555+ :param show_alignment: If True, append a rightmost alignment column
538556 :return: The figure object for the plot
539557 """
540558
541559 # Set the matplotlib configs
542560 set_themes (choice = "plot_imfs" )
543561
562+ signal = np .asarray (signal , dtype = float )
563+ IMFs = np .asarray (IMFs , dtype = float )
564+ if signal .ndim != 2 :
565+ raise ValueError ("signal must have shape (n_vars, seq_len)" )
566+ if IMFs .ndim != 3 :
567+ raise ValueError ("IMFs must have shape (n_imfs, seq_len, n_vars)" )
568+
544569 # Get the number of elements and signal length of a multi-element signal
545570 n_vars , seq_len = signal .shape
571+ if IMFs .shape [1 ] != seq_len or IMFs .shape [2 ] != n_vars :
572+ raise ValueError (
573+ "IMFs shape must be (n_imfs, seq_len, n_vars) matching the signal"
574+ )
546575
547576 # Edge padding
548577 padding = int (seq_len / 50 )
549578
550579 # Determine the number of rows
551580 if max_imfs == - 1 :
552- n_rows = IMFs .shape [0 ] + 1
581+ n_imfs = IMFs .shape [0 ]
553582 else :
554- n_rows = min (max_imfs , IMFs .shape [0 ]) + 1
583+ n_imfs = min (int (max_imfs ), IMFs .shape [0 ])
584+ n_rows = n_imfs + 1
555585
556- # Reconstruct the input signal
586+ # Reconstruct the input signal stack: (n_rows, seq_len, n_vars)
557587 signals = np .zeros (shape = (n_rows , seq_len , n_vars ))
558588 signals [0 , :, :] = signal .transpose (1 , 0 )
559- signals [1 :, :, :] = IMFs
589+ signals [1 :, :, :] = IMFs [: n_imfs ]
560590
561591 # Set the colors for plotting
562592 if colors is None :
563- colors = COLORS
593+ colors = list (COLORS )
594+ else :
595+ colors = list (colors )
564596 # Add random colors if there are not enough colors in the list
565597 while len (colors ) <= n_rows :
566598 colors .append (generate_random_hex_color ())
567599
568- # Create the figure and axes for multi-plotting
600+ if channel_colors is None :
601+ channel_colors = [
602+ "#1f4e79" ,
603+ "#c45c26" ,
604+ "#2a9d8f" ,
605+ "#6c5ce7" ,
606+ "#d63031" ,
607+ "#00b894" ,
608+ ]
609+ else :
610+ channel_colors = list (channel_colors )
611+ while len (channel_colors ) < n_vars :
612+ channel_colors .append (generate_random_hex_color ())
613+
614+ n_cols = n_vars + (1 if show_alignment else 0 )
569615 fig , ax = plt .subplots (
570- nrows = n_rows , ncols = n_vars , figsize = (3 * n_vars , 1.5 * n_rows - 1 ), dpi = 256
616+ nrows = n_rows ,
617+ ncols = n_cols ,
618+ figsize = (3 * n_vars + (3.2 if show_alignment else 0 ), 1.5 * n_rows - 1 ),
619+ dpi = 256 ,
620+ squeeze = False ,
571621 )
572622 fig .tight_layout ()
573623
@@ -603,7 +653,33 @@ def plot_multi_IMFs(
603653 if row != n_rows - 1 :
604654 ax [row , col ].set_xticks ([])
605655
606- for col in range (n_vars ):
656+ if show_alignment :
657+ ax_a = ax [row , n_vars ]
658+ ax_a .axhline (
659+ y = 0 , color = "gray" , linestyle = "-" , alpha = 0.6 , linewidth = spine_width
660+ )
661+ for c in range (n_vars ):
662+ ax_a .plot (
663+ signals [row , :, c ],
664+ color = channel_colors [c ],
665+ lw = 0.9 ,
666+ alpha = 0.9 ,
667+ label = f"ch{ c + 1 } " ,
668+ )
669+ ax_a .set_xlim (- padding , seq_len + padding )
670+ ax_a .tick_params (axis = "both" , which = "major" , labelsize = 8 )
671+ for spine_name , spine in ax_a .spines .items ():
672+ if spine_name != "left" :
673+ spine .set_visible (False )
674+ else :
675+ spine .set_visible (True )
676+ spine .set_linewidth (spine_width )
677+ if row != n_rows - 1 :
678+ ax_a .set_xticks ([])
679+ if row == 0 :
680+ ax_a .legend (fontsize = 7 , loc = "upper right" , frameon = False )
681+
682+ for col in range (n_cols ):
607683 # Open the bottom spine of the last axes and set its position
608684 ax [- 1 , col ].spines ["bottom" ].set_position (("axes" , - 0.1 ))
609685 ax [- 1 , col ].spines ["bottom" ].set_visible (True )
@@ -619,6 +695,8 @@ def plot_multi_IMFs(
619695 # Setting the number of variations
620696 for col in range (n_vars ):
621697 ax [0 , col ].set_title (f"Var-{ col } " , fontsize = fontsize + 1 )
698+ if show_alignment :
699+ ax [0 , n_vars ].set_title ("Align" , fontsize = fontsize + 1 )
622700
623701 # Save the figure if requested
624702 saved = False
@@ -637,6 +715,7 @@ def plot_multi_IMFs(
637715 # Return the figure if requested
638716 if return_figure is True :
639717 return fig
718+ return None
640719
641720
642721def plot_multi_3D_IMFs (
0 commit comments