modusa 0.3.42__py3-none-any.whl → 0.3.43__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modusa/__init__.py +1 -1
- modusa/tools/__init__.py +5 -2
- modusa/tools/_plotter_old.py +629 -0
- modusa/tools/plotter.py +663 -394
- {modusa-0.3.42.dist-info → modusa-0.3.43.dist-info}/METADATA +2 -2
- {modusa-0.3.42.dist-info → modusa-0.3.43.dist-info}/RECORD +9 -8
- {modusa-0.3.42.dist-info → modusa-0.3.43.dist-info}/WHEEL +0 -0
- {modusa-0.3.42.dist-info → modusa-0.3.43.dist-info}/entry_points.txt +0 -0
- {modusa-0.3.42.dist-info → modusa-0.3.43.dist-info}/licenses/LICENSE.md +0 -0
    
        modusa/__init__.py
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            from modusa.utils import excp, config
         | 
| 2 2 |  | 
| 3 3 | 
             
            #=====Giving access to plot functions to plot multiple signals.=====
         | 
| 4 | 
            -
            from modusa.tools import  | 
| 4 | 
            +
            from modusa.tools import fig1d, fig2d, plot_dist
         | 
| 5 5 | 
             
            #=====
         | 
| 6 6 |  | 
| 7 7 | 
             
            from modusa.tools import play, convert, record
         | 
    
        modusa/tools/__init__.py
    CHANGED
    
    | @@ -1,9 +1,12 @@ | |
| 1 1 | 
             
            #!/usr/bin/env python3
         | 
| 2 2 |  | 
| 3 | 
            -
            from .plotter import plot1d, plot2d, plot_dist
         | 
| 4 3 | 
             
            from .audio_player import play
         | 
| 5 4 | 
             
            from .audio_converter import convert
         | 
| 6 5 | 
             
            from .youtube_downloader import download
         | 
| 7 6 | 
             
            from .audio_loader import load
         | 
| 8 7 | 
             
            from .ann_loader import load_ann
         | 
| 9 | 
            -
            from .audio_recorder import record
         | 
| 8 | 
            +
            from .audio_recorder import record
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            from .plotter import Figure1D as fig1d
         | 
| 11 | 
            +
            from .plotter import Figure2D as fig2d
         | 
| 12 | 
            +
            from .plotter import plot_dist
         | 
| @@ -0,0 +1,629 @@ | |
| 1 | 
            +
            #!/usr/bin/env python3
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            import numpy as np
         | 
| 5 | 
            +
            import matplotlib.pyplot as plt
         | 
| 6 | 
            +
            import matplotlib.gridspec as gridspec
         | 
| 7 | 
            +
            from matplotlib.patches import Rectangle
         | 
| 8 | 
            +
            from mpl_toolkits.axes_grid1.inset_locator import inset_axes
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # Helper for 2D plot
         | 
| 11 | 
            +
            def _calculate_extent(x, y):
         | 
| 12 | 
            +
            	# Handle spacing safely
         | 
| 13 | 
            +
            	if len(x) > 1:
         | 
| 14 | 
            +
            		dx = x[1] - x[0]
         | 
| 15 | 
            +
            	else:
         | 
| 16 | 
            +
            		dx = 1  # Default spacing for single value
         | 
| 17 | 
            +
            	if len(y) > 1:
         | 
| 18 | 
            +
            		dy = y[1] - y[0]
         | 
| 19 | 
            +
            	else:
         | 
| 20 | 
            +
            		dy = 1  # Default spacing for single value
         | 
| 21 | 
            +
            		
         | 
| 22 | 
            +
            	return [
         | 
| 23 | 
            +
            		x[0] - dx / 2,
         | 
| 24 | 
            +
            		x[-1] + dx / 2,
         | 
| 25 | 
            +
            		y[0] - dy / 2,
         | 
| 26 | 
            +
            		y[-1] + dy / 2
         | 
| 27 | 
            +
            	]
         | 
| 28 | 
            +
            	
         | 
| 29 | 
            +
            # Helper to load fonts (devnagri)
         | 
| 30 | 
            +
            def set_default_hindi_font():
         | 
| 31 | 
            +
            	"""
         | 
| 32 | 
            +
            	Hindi fonts works for both english and hindi.
         | 
| 33 | 
            +
            	"""
         | 
| 34 | 
            +
            	from pathlib import Path
         | 
| 35 | 
            +
            	import matplotlib as mpl
         | 
| 36 | 
            +
            	import matplotlib.font_manager as fm
         | 
| 37 | 
            +
            	# Path to your bundled font
         | 
| 38 | 
            +
            	font_path = Path(__file__).resolve().parents[1] / "fonts" / "NotoSansDevanagari-Regular.ttf"
         | 
| 39 | 
            +
            	
         | 
| 40 | 
            +
            	# Register the font with matplotlib
         | 
| 41 | 
            +
            	fm.fontManager.addfont(str(font_path))
         | 
| 42 | 
            +
            	
         | 
| 43 | 
            +
            	# Get the font family name from the file
         | 
| 44 | 
            +
            	hindi_font = fm.FontProperties(fname=str(font_path))
         | 
| 45 | 
            +
            	
         | 
| 46 | 
            +
            	# Set as default rcParam
         | 
| 47 | 
            +
            	mpl.rcParams['font.family'] = hindi_font.get_name()
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            #set_default_hindi_font()
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            #======== 1D ===========
         | 
| 52 | 
            +
            def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylabel=None, title=None, legend=None, fmt=None, show_grid=False, show_stem=False):
         | 
| 53 | 
            +
            		"""
         | 
| 54 | 
            +
            		Plots a 1D signal using matplotlib.
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            		.. code-block:: python
         | 
| 57 | 
            +
            	
         | 
| 58 | 
            +
            			import modusa as ms
         | 
| 59 | 
            +
            			import numpy as np
         | 
| 60 | 
            +
            			
         | 
| 61 | 
            +
            			x = np.arange(100) / 100
         | 
| 62 | 
            +
            			y = np.sin(x)
         | 
| 63 | 
            +
            			
         | 
| 64 | 
            +
            			display(ms.plot1d(y, x))
         | 
| 65 | 
            +
            			
         | 
| 66 | 
            +
            	
         | 
| 67 | 
            +
            		Parameters
         | 
| 68 | 
            +
            		----------
         | 
| 69 | 
            +
            		*args : tuple[array-like, array-like] | tuple[array-like]
         | 
| 70 | 
            +
            			- The signal y and axis x to be plotted.
         | 
| 71 | 
            +
            			- If only values are provided, we generate the axis using arange.
         | 
| 72 | 
            +
            			- E.g. (y1, x1), (y2, x2), ...
         | 
| 73 | 
            +
            		ann : list[tuple[Number, Number, str] | None
         | 
| 74 | 
            +
            			- A list of annotations to mark specific points. Each tuple should be of the form (start, end, label).
         | 
| 75 | 
            +
            			- Default: None => No annotation.
         | 
| 76 | 
            +
            		events : list[Number] | None
         | 
| 77 | 
            +
            			- A list of x-values where vertical lines (event markers) will be drawn.
         | 
| 78 | 
            +
            			- Default: None
         | 
| 79 | 
            +
            		xlim : tuple[Number, Number] | None
         | 
| 80 | 
            +
            			- Limits for the x-axis as (xmin, xmax).
         | 
| 81 | 
            +
            			- Default: None
         | 
| 82 | 
            +
            		ylim : tuple[Number, Number] | None
         | 
| 83 | 
            +
            			- Limits for the y-axis as (ymin, ymax).
         | 
| 84 | 
            +
            			- Default: None
         | 
| 85 | 
            +
            		xlabel : str | None
         | 
| 86 | 
            +
            			- Label for the x-axis.
         | 
| 87 | 
            +
            			- - Default: None
         | 
| 88 | 
            +
            		ylabel : str | None
         | 
| 89 | 
            +
            			- Label for the y-axis.
         | 
| 90 | 
            +
            			- Default: None
         | 
| 91 | 
            +
            		title : str | None
         | 
| 92 | 
            +
            			- Title of the plot.
         | 
| 93 | 
            +
            			- Default: None
         | 
| 94 | 
            +
            		legend : list[str] | None
         | 
| 95 | 
            +
            			- List of legend labels corresponding to each signal if plotting multiple lines.
         | 
| 96 | 
            +
            			- Default: None
         | 
| 97 | 
            +
            		fmt: list[str] | None
         | 
| 98 | 
            +
            			- linefmt for different line plots.
         | 
| 99 | 
            +
            			- Default: None
         | 
| 100 | 
            +
            		show_grid: bool
         | 
| 101 | 
            +
            			- If you want to show the grid.
         | 
| 102 | 
            +
            			- Default: False
         | 
| 103 | 
            +
            		show_stem: bool:
         | 
| 104 | 
            +
            			- If you want stem plot.
         | 
| 105 | 
            +
            			- Default: False
         | 
| 106 | 
            +
            	
         | 
| 107 | 
            +
            		Returns
         | 
| 108 | 
            +
            		-------
         | 
| 109 | 
            +
            		plt.Figure
         | 
| 110 | 
            +
            			Matplolib figure.
         | 
| 111 | 
            +
            		"""
         | 
| 112 | 
            +
            		for arg in args:
         | 
| 113 | 
            +
            			if len(arg) not in [1, 2]: # 1 if it just provides values, 2 if it provided axis as well
         | 
| 114 | 
            +
            				raise ValueError(f"1D signal needs to have max 2 arrays (y, x) or simply (y, )")
         | 
| 115 | 
            +
            			
         | 
| 116 | 
            +
            		if isinstance(legend, str): legend = (legend, )
         | 
| 117 | 
            +
            		if legend is not None:
         | 
| 118 | 
            +
            			if len(legend) < len(args):
         | 
| 119 | 
            +
            				raise ValueError(f"`legend` should be provided for each signal.")
         | 
| 120 | 
            +
            		
         | 
| 121 | 
            +
            		if isinstance(fmt, str): fmt = [fmt]
         | 
| 122 | 
            +
            		if fmt is not None:
         | 
| 123 | 
            +
            			if len(fmt) < len(args):
         | 
| 124 | 
            +
            				raise ValueError(f"`fmt` should be provided for each signal.")
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            		colors = plt.get_cmap('tab10').colors
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            		fig = plt.figure(figsize=(16, 2))
         | 
| 129 | 
            +
            		gs = gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.2, 1])
         | 
| 130 | 
            +
            		
         | 
| 131 | 
            +
            		signal_ax = fig.add_subplot(gs[2, 0])
         | 
| 132 | 
            +
            		annotation_ax = fig.add_subplot(gs[0, 0], sharex=signal_ax)
         | 
| 133 | 
            +
            		events_ax = fig.add_subplot(gs[1, 0])
         | 
| 134 | 
            +
            		
         | 
| 135 | 
            +
            		# Set lim
         | 
| 136 | 
            +
            		if xlim is not None:
         | 
| 137 | 
            +
            			signal_ax.set_xlim(xlim)
         | 
| 138 | 
            +
            		
         | 
| 139 | 
            +
            		if ylim is not None:
         | 
| 140 | 
            +
            			signal_ax.set_ylim(ylim)
         | 
| 141 | 
            +
            			
         | 
| 142 | 
            +
            		# Add signal plot
         | 
| 143 | 
            +
            		for i, signal in enumerate(args):
         | 
| 144 | 
            +
            			if len(signal) == 1:
         | 
| 145 | 
            +
            				y = signal[0]
         | 
| 146 | 
            +
            				x = np.arange(y.size)
         | 
| 147 | 
            +
            				if legend is not None:
         | 
| 148 | 
            +
            					if show_stem is True:
         | 
| 149 | 
            +
            						markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
         | 
| 150 | 
            +
            						markerline.set_color(colors[i])
         | 
| 151 | 
            +
            						stemlines.set_color(colors[i])
         | 
| 152 | 
            +
            						baseline.set_color("k")
         | 
| 153 | 
            +
            					else:
         | 
| 154 | 
            +
            						if fmt is not None:
         | 
| 155 | 
            +
            							signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
         | 
| 156 | 
            +
            						else:
         | 
| 157 | 
            +
            							signal_ax.plot(x, y, color=colors[i], label=legend[i])
         | 
| 158 | 
            +
            				else:
         | 
| 159 | 
            +
            					if show_stem is True:
         | 
| 160 | 
            +
            						markerline, stemlines, baseline = signal_ax.stem(x, y)
         | 
| 161 | 
            +
            						markerline.set_color(colors[i])
         | 
| 162 | 
            +
            						stemlines.set_color(colors[i])
         | 
| 163 | 
            +
            						baseline.set_color("k")
         | 
| 164 | 
            +
            					else:
         | 
| 165 | 
            +
            						if fmt is not None:
         | 
| 166 | 
            +
            							signal_ax.plot(x, y, fmt[i], markersize=4)
         | 
| 167 | 
            +
            						else:
         | 
| 168 | 
            +
            							signal_ax.plot(x, y, color=colors[i])
         | 
| 169 | 
            +
            						
         | 
| 170 | 
            +
            			elif len(signal) == 2:
         | 
| 171 | 
            +
            				y, x = signal[0], signal[1]
         | 
| 172 | 
            +
            				if legend is not None:
         | 
| 173 | 
            +
            					if show_stem is True:
         | 
| 174 | 
            +
            						markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
         | 
| 175 | 
            +
            						markerline.set_color(colors[i])
         | 
| 176 | 
            +
            						stemlines.set_color(colors[i])
         | 
| 177 | 
            +
            						baseline.set_color("k")
         | 
| 178 | 
            +
            					else:
         | 
| 179 | 
            +
            						if fmt is not None:
         | 
| 180 | 
            +
            							signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
         | 
| 181 | 
            +
            						else:
         | 
| 182 | 
            +
            							signal_ax.plot(x, y, color=colors[i], label=legend[i])
         | 
| 183 | 
            +
            				else:
         | 
| 184 | 
            +
            					if show_stem is True:
         | 
| 185 | 
            +
            						markerline, stemlines, baseline = signal_ax.stem(x, y)
         | 
| 186 | 
            +
            						markerline.set_color(colors[i])
         | 
| 187 | 
            +
            						stemlines.set_color(colors[i])
         | 
| 188 | 
            +
            						baseline.set_color("k")
         | 
| 189 | 
            +
            					else:
         | 
| 190 | 
            +
            						if fmt is not None:
         | 
| 191 | 
            +
            							signal_ax.plot(x, y, fmt[i], markersize=4)
         | 
| 192 | 
            +
            						else:
         | 
| 193 | 
            +
            							signal_ax.plot(x, y, color=colors[i])
         | 
| 194 | 
            +
            							
         | 
| 195 | 
            +
            		
         | 
| 196 | 
            +
            		# Add annotations
         | 
| 197 | 
            +
            		if ann is not None:
         | 
| 198 | 
            +
            			annotation_ax.set_ylim(0, 1) # For consistent layout
         | 
| 199 | 
            +
            			# Determine visible x-range
         | 
| 200 | 
            +
            			x_view_min = xlim[0] if xlim is not None else np.min(x)
         | 
| 201 | 
            +
            			x_view_max = xlim[1] if xlim is not None else np.max(x)
         | 
| 202 | 
            +
            			
         | 
| 203 | 
            +
            			for i, (start, end, tag) in enumerate(ann):
         | 
| 204 | 
            +
            				# We make sure that we only plot annotation that are within the x range of the current view
         | 
| 205 | 
            +
            				if start >= x_view_max or end <= x_view_min:
         | 
| 206 | 
            +
            					continue
         | 
| 207 | 
            +
            				
         | 
| 208 | 
            +
            				# Clip boundaries to xlim
         | 
| 209 | 
            +
            				start = max(start, x_view_min)
         | 
| 210 | 
            +
            				end = min(end, x_view_max)
         | 
| 211 | 
            +
            				
         | 
| 212 | 
            +
            				box_colors = ["gray", "lightgray"] # Alternates color between two
         | 
| 213 | 
            +
            				box_color = box_colors[i % 2]
         | 
| 214 | 
            +
            				
         | 
| 215 | 
            +
            				width = end - start
         | 
| 216 | 
            +
            				rect = Rectangle((start, 0), width, 1, color=box_color, alpha=0.7)
         | 
| 217 | 
            +
            				annotation_ax.add_patch(rect)
         | 
| 218 | 
            +
            				
         | 
| 219 | 
            +
            				text_obj = annotation_ax.text(
         | 
| 220 | 
            +
            					(start + end) / 2, 0.5, tag,
         | 
| 221 | 
            +
            					ha='center', va='center',
         | 
| 222 | 
            +
            					fontsize=10, color="black", fontweight='bold', zorder=10, clip_on=True
         | 
| 223 | 
            +
            				)
         | 
| 224 | 
            +
            				
         | 
| 225 | 
            +
            				text_obj.set_clip_path(rect)
         | 
| 226 | 
            +
            				
         | 
| 227 | 
            +
            				
         | 
| 228 | 
            +
            		# Add vlines
         | 
| 229 | 
            +
            		if events is not None:
         | 
| 230 | 
            +
            #			if not isinstance(events, tuple):
         | 
| 231 | 
            +
            #				raise TypeError(f"`events` should be tuple, got {type(events)}")
         | 
| 232 | 
            +
            			
         | 
| 233 | 
            +
            			for xpos in events:
         | 
| 234 | 
            +
            				if xlim is not None:
         | 
| 235 | 
            +
            					if xlim[0] <= xpos <= xlim[1]:
         | 
| 236 | 
            +
            						annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
         | 
| 237 | 
            +
            				else:
         | 
| 238 | 
            +
            					annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
         | 
| 239 | 
            +
            					
         | 
| 240 | 
            +
            		# Add legend
         | 
| 241 | 
            +
            		if legend is not None:
         | 
| 242 | 
            +
            			handles, labels = signal_ax.get_legend_handles_labels()
         | 
| 243 | 
            +
            			fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.2), ncol=len(legend), frameon=True)
         | 
| 244 | 
            +
            			
         | 
| 245 | 
            +
            		# Set title, labels
         | 
| 246 | 
            +
            		if title is not None:
         | 
| 247 | 
            +
            			annotation_ax.set_title(title, pad=10, size=11)
         | 
| 248 | 
            +
            		if xlabel is not None:
         | 
| 249 | 
            +
            			signal_ax.set_xlabel(xlabel)
         | 
| 250 | 
            +
            		if ylabel is not None:
         | 
| 251 | 
            +
            			signal_ax.set_ylabel(ylabel)
         | 
| 252 | 
            +
            			
         | 
| 253 | 
            +
            		# Add grid to the plot
         | 
| 254 | 
            +
            		if show_grid is True:
         | 
| 255 | 
            +
            			signal_ax.grid(True, linestyle=':', linewidth=0.7, color='gray', alpha=0.7)
         | 
| 256 | 
            +
            		
         | 
| 257 | 
            +
            		# Remove the boundaries and ticks from an axis
         | 
| 258 | 
            +
            		if ann is not None:
         | 
| 259 | 
            +
            			annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
         | 
| 260 | 
            +
            		else:
         | 
| 261 | 
            +
            			annotation_ax.axis("off")
         | 
| 262 | 
            +
            			
         | 
| 263 | 
            +
            		if events is not None:
         | 
| 264 | 
            +
            			events_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
         | 
| 265 | 
            +
            		else:
         | 
| 266 | 
            +
            			events_ax.axis("off")
         | 
| 267 | 
            +
            		
         | 
| 268 | 
            +
            		
         | 
| 269 | 
            +
            		fig.subplots_adjust(hspace=0.01, wspace=0.05)
         | 
| 270 | 
            +
            		plt.close()
         | 
| 271 | 
            +
            		return fig
         | 
| 272 | 
            +
             | 
| 273 | 
            +
            #======== 2D ===========
         | 
| 274 | 
            +
            def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", Mlabel=None, xlabel=None, ylabel=None, title=None, legend=None, lm=False, show_grid=False):
         | 
| 275 | 
            +
            	"""
         | 
| 276 | 
            +
            	Plots a 2D matrix (e.g., spectrogram or heatmap) with optional annotations and events.
         | 
| 277 | 
            +
             | 
| 278 | 
            +
            	.. code-block:: python
         | 
| 279 | 
            +
             | 
| 280 | 
            +
            		import modusa as ms
         | 
| 281 | 
            +
            		import numpy as np
         | 
| 282 | 
            +
            		
         | 
| 283 | 
            +
            		M = np.random.random((10, 30))
         | 
| 284 | 
            +
            		y = np.arange(M.shape[0])
         | 
| 285 | 
            +
            		x = np.arange(M.shape[1])
         | 
| 286 | 
            +
            		
         | 
| 287 | 
            +
            		display(ms.plot2d(M, y, x))
         | 
| 288 | 
            +
             | 
| 289 | 
            +
            	Parameters
         | 
| 290 | 
            +
            	----------
         | 
| 291 | 
            +
            	*args : tuple[array-like, array-like]
         | 
| 292 | 
            +
            		- The signal values to be plotted.
         | 
| 293 | 
            +
            		- E.g. (M1, y1, x1), (M2, y2, x2), ...
         | 
| 294 | 
            +
            	ann : list[tuple[Number, Number, str]] | None
         | 
| 295 | 
            +
            		- A list of annotation spans. Each tuple should be (start, end, label).
         | 
| 296 | 
            +
            		- Default: None (no annotations).
         | 
| 297 | 
            +
            	events : list[Number] | None
         | 
| 298 | 
            +
            		- X-values where vertical event lines will be drawn.
         | 
| 299 | 
            +
            		- Default: None.
         | 
| 300 | 
            +
            	xlim : tuple[Number, Number] | None
         | 
| 301 | 
            +
            		- Limits for the x-axis as (xmin, xmax).
         | 
| 302 | 
            +
            		- Default: None (auto-scaled).
         | 
| 303 | 
            +
            	ylim : tuple[Number, Number] | None
         | 
| 304 | 
            +
            		- Limits for the y-axis as (ymin, ymax).
         | 
| 305 | 
            +
            		- Default: None (auto-scaled).
         | 
| 306 | 
            +
            	origin : {'upper', 'lower'}
         | 
| 307 | 
            +
            		- Origin position for the image display. Used in `imshow`.
         | 
| 308 | 
            +
            		- Default: "lower".
         | 
| 309 | 
            +
            	Mlabel : str | None
         | 
| 310 | 
            +
            		- Label for the colorbar (e.g., "Magnitude", "Energy").
         | 
| 311 | 
            +
            		- Default: None.
         | 
| 312 | 
            +
            	xlabel : str | None
         | 
| 313 | 
            +
            		- Label for the x-axis.
         | 
| 314 | 
            +
            		- Default: None.
         | 
| 315 | 
            +
            	ylabel : str | None
         | 
| 316 | 
            +
            		- Label for the y-axis.
         | 
| 317 | 
            +
            		- Default: None.
         | 
| 318 | 
            +
            	title : str | None
         | 
| 319 | 
            +
            		- Title of the plot.
         | 
| 320 | 
            +
            		- Default: None.
         | 
| 321 | 
            +
            	legend : list[str] | None
         | 
| 322 | 
            +
            		- Legend labels for any overlaid lines or annotations.
         | 
| 323 | 
            +
            		- Default: None.
         | 
| 324 | 
            +
            	lm: bool
         | 
| 325 | 
            +
            		- Adds a circular marker for the line.
         | 
| 326 | 
            +
            		- Default: False
         | 
| 327 | 
            +
            		- Useful to show the data points.
         | 
| 328 | 
            +
            	show_grid: bool
         | 
| 329 | 
            +
            		- If you want to show the grid.
         | 
| 330 | 
            +
            		- Default: False
         | 
| 331 | 
            +
             | 
| 332 | 
            +
            	Returns
         | 
| 333 | 
            +
            	-------
         | 
| 334 | 
            +
            	matplotlib.figure.Figure
         | 
| 335 | 
            +
            		The matplotlib Figure object.
         | 
| 336 | 
            +
            	"""
         | 
| 337 | 
            +
            	
         | 
| 338 | 
            +
            	for arg in args:
         | 
| 339 | 
            +
            		if len(arg) not in [1, 2, 3]: # Either provide just the matrix or with both axes info
         | 
| 340 | 
            +
            			raise ValueError(f"Data to plot needs to have 3 arrays (M, y, x)")
         | 
| 341 | 
            +
            	if isinstance(legend, str): legend = (legend, )
         | 
| 342 | 
            +
            	
         | 
| 343 | 
            +
            	fig = plt.figure(figsize=(16, 4))
         | 
| 344 | 
            +
            	gs = gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.1, 1]) # colorbar, annotation, signal
         | 
| 345 | 
            +
             | 
| 346 | 
            +
            	colors = plt.get_cmap('tab10').colors
         | 
| 347 | 
            +
            	
         | 
| 348 | 
            +
            	signal_ax = fig.add_subplot(gs[2, 0])
         | 
| 349 | 
            +
            	annotation_ax = fig.add_subplot(gs[1, 0], sharex=signal_ax)
         | 
| 350 | 
            +
            	
         | 
| 351 | 
            +
            	colorbar_ax = fig.add_subplot(gs[0, 0])
         | 
| 352 | 
            +
            	colorbar_ax.axis("off")
         | 
| 353 | 
            +
            	
         | 
| 354 | 
            +
            	
         | 
| 355 | 
            +
            	# Add lim
         | 
| 356 | 
            +
            	if xlim is not None:
         | 
| 357 | 
            +
            		signal_ax.set_xlim(xlim)
         | 
| 358 | 
            +
            		
         | 
| 359 | 
            +
            	if ylim is not None:
         | 
| 360 | 
            +
            		signal_ax.set_ylim(ylim)
         | 
| 361 | 
            +
            		
         | 
| 362 | 
            +
            	# Add signal plot
         | 
| 363 | 
            +
            	i = 0 # This is to track the legend for 1D plots
         | 
| 364 | 
            +
            	for signal in args:
         | 
| 365 | 
            +
            		
         | 
| 366 | 
            +
            		data = signal[0] # This can be 1D or 2D (1D meaning we have to overlay on the matrix)
         | 
| 367 | 
            +
            			
         | 
| 368 | 
            +
            		if data.ndim == 1: # 1D
         | 
| 369 | 
            +
            			if len(signal) == 1: # It means that the axis was not passed
         | 
| 370 | 
            +
            				x = np.arange(data.shape[0])
         | 
| 371 | 
            +
            			else:
         | 
| 372 | 
            +
            				x = signal[1]
         | 
| 373 | 
            +
            			
         | 
| 374 | 
            +
            			if lm is False:
         | 
| 375 | 
            +
            				if legend is not None:
         | 
| 376 | 
            +
            					signal_ax.plot(x, data, label=legend[i])
         | 
| 377 | 
            +
            					signal_ax.legend(loc="upper right")
         | 
| 378 | 
            +
            				else:
         | 
| 379 | 
            +
            					signal_ax.plot(x, data)
         | 
| 380 | 
            +
            			else:
         | 
| 381 | 
            +
            				if legend is not None:
         | 
| 382 | 
            +
            					signal_ax.plot(x, data, marker="o", markersize=7, markerfacecolor='red', linestyle="--", linewidth=2, label=legend[i])
         | 
| 383 | 
            +
            					signal_ax.legend(loc="upper right")
         | 
| 384 | 
            +
            				else:
         | 
| 385 | 
            +
            					signal_ax.plot(x, data, marker="o", markersize=7, markerfacecolor='red', linestyle="--", linewidth=2)
         | 
| 386 | 
            +
            					
         | 
| 387 | 
            +
            			i += 1
         | 
| 388 | 
            +
            			
         | 
| 389 | 
            +
            		elif data.ndim == 2: # 2D
         | 
| 390 | 
            +
            			M = data
         | 
| 391 | 
            +
            			if len(signal) == 1: # It means that the axes were not passed
         | 
| 392 | 
            +
            				y = np.arange(M.shape[0])
         | 
| 393 | 
            +
            				x = np.arange(M.shape[1])
         | 
| 394 | 
            +
            				extent = _calculate_extent(x, y)
         | 
| 395 | 
            +
            				im = signal_ax.imshow(M, aspect="auto", origin=origin, cmap="gray_r", extent=extent)
         | 
| 396 | 
            +
            				
         | 
| 397 | 
            +
            			elif len(signal) == 3: # It means that the axes were passed
         | 
| 398 | 
            +
            				M, y, x = signal[0], signal[1], signal[2]
         | 
| 399 | 
            +
            				extent = _calculate_extent(x, y)
         | 
| 400 | 
            +
            				im = signal_ax.imshow(M, aspect="auto", origin=origin, cmap="gray_r", extent=extent)
         | 
| 401 | 
            +
            	
         | 
| 402 | 
            +
            	# Add annotations
         | 
| 403 | 
            +
            	if ann is not None:
         | 
| 404 | 
            +
            		annotation_ax.set_ylim(0, 1) # For consistent layout
         | 
| 405 | 
            +
            		# Determine visible x-range
         | 
| 406 | 
            +
            		x_view_min = xlim[0] if xlim is not None else np.min(x)
         | 
| 407 | 
            +
            		x_view_max = xlim[1] if xlim is not None else np.max(x)
         | 
| 408 | 
            +
            		
         | 
| 409 | 
            +
            		for i, (start, end, tag) in enumerate(ann):
         | 
| 410 | 
            +
            			# We make sure that we only plot annotation that are within the x range of the current view
         | 
| 411 | 
            +
            			if start >= x_view_max or end <= x_view_min:
         | 
| 412 | 
            +
            				continue
         | 
| 413 | 
            +
            			
         | 
| 414 | 
            +
            			# Clip boundaries to xlim
         | 
| 415 | 
            +
            			start = max(start, x_view_min)
         | 
| 416 | 
            +
            			end = min(end, x_view_max)
         | 
| 417 | 
            +
            				
         | 
| 418 | 
            +
            			color = colors[i % len(colors)]
         | 
| 419 | 
            +
            			width = end - start
         | 
| 420 | 
            +
            			rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
         | 
| 421 | 
            +
            			annotation_ax.add_patch(rect)
         | 
| 422 | 
            +
            			text_obj = annotation_ax.text(
         | 
| 423 | 
            +
            				(start + end) / 2, 0.5, tag,
         | 
| 424 | 
            +
            				ha='center', va='center',
         | 
| 425 | 
            +
            				fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True
         | 
| 426 | 
            +
            			)
         | 
| 427 | 
            +
            			
         | 
| 428 | 
            +
            			text_obj.set_clip_path(rect)
         | 
| 429 | 
            +
            			
         | 
| 430 | 
            +
            	# Add vlines
         | 
| 431 | 
            +
            	if events is not None:
         | 
| 432 | 
            +
            		for xpos in events:
         | 
| 433 | 
            +
            			if xlim is not None:
         | 
| 434 | 
            +
            				if xlim[0] <= xpos <= xlim[1]:
         | 
| 435 | 
            +
            					annotation_ax.axvline(x=xpos, color='black', linestyle='--', linewidth=1.5)
         | 
| 436 | 
            +
            			else:
         | 
| 437 | 
            +
            				annotation_ax.axvline(x=xpos, color='black', linestyle='--', linewidth=1.5)
         | 
| 438 | 
            +
            	
         | 
| 439 | 
            +
            	# Add legend incase there are 1D overlays
         | 
| 440 | 
            +
            	if legend is not None:
         | 
| 441 | 
            +
            		handles, labels = signal_ax.get_legend_handles_labels()
         | 
| 442 | 
            +
            		if handles:  # Only add legend if there's something to show
         | 
| 443 | 
            +
            			signal_ax.legend(handles, labels, loc="upper right")
         | 
| 444 | 
            +
            	
         | 
| 445 | 
            +
            	# Add colorbar
         | 
| 446 | 
            +
            	# Create an inset axis on top-right of signal_ax
         | 
| 447 | 
            +
            	cax = inset_axes(
         | 
| 448 | 
            +
            		colorbar_ax,
         | 
| 449 | 
            +
            		width="20%",      # percentage of parent width
         | 
| 450 | 
            +
            		height="20%",      # height in percentage of parent height
         | 
| 451 | 
            +
            		loc='upper right',
         | 
| 452 | 
            +
            		bbox_to_anchor=(0, 0, 1, 1),
         | 
| 453 | 
            +
            		bbox_transform=colorbar_ax.transAxes,
         | 
| 454 | 
            +
            		borderpad=1
         | 
| 455 | 
            +
            	)
         | 
| 456 | 
            +
            	
         | 
| 457 | 
            +
            	cbar = plt.colorbar(im, cax=cax, orientation='horizontal')
         | 
| 458 | 
            +
            	cbar.ax.xaxis.set_ticks_position('top')
         | 
| 459 | 
            +
            	
         | 
| 460 | 
            +
            	if Mlabel is not None:
         | 
| 461 | 
            +
            		cbar.set_label(Mlabel, labelpad=5)
         | 
| 462 | 
            +
            	
         | 
| 463 | 
            +
            		
         | 
| 464 | 
            +
            	# Set title, labels
         | 
| 465 | 
            +
            	if title is not None:
         | 
| 466 | 
            +
            		annotation_ax.set_title(title, pad=10, size=11)
         | 
| 467 | 
            +
            	if xlabel is not None:
         | 
| 468 | 
            +
            		signal_ax.set_xlabel(xlabel)
         | 
| 469 | 
            +
            	if ylabel is not None:
         | 
| 470 | 
            +
            		signal_ax.set_ylabel(ylabel)
         | 
| 471 | 
            +
            		
         | 
| 472 | 
            +
            	# Add grid to the plot
         | 
| 473 | 
            +
            	if show_grid is True:
         | 
| 474 | 
            +
            		signal_ax.grid(True, linestyle=':', linewidth=0.7, color='gray', alpha=0.7)
         | 
| 475 | 
            +
            	
         | 
| 476 | 
            +
            	# Making annotation axis spines thicker
         | 
| 477 | 
            +
            	if ann is not None:
         | 
| 478 | 
            +
            		annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
         | 
| 479 | 
            +
            	else:
         | 
| 480 | 
            +
            		annotation_ax.axis("off")
         | 
| 481 | 
            +
             | 
| 482 | 
            +
            	fig.subplots_adjust(hspace=0.01, wspace=0.05)
         | 
| 483 | 
            +
            	plt.close()
         | 
| 484 | 
            +
            	return fig
         | 
| 485 | 
            +
             | 
| 486 | 
            +
            #======== Plot distribution ===========
         | 
| 487 | 
            +
            def plot_dist(*args, ann=None, xlim=None, ylim=None, ylabel=None, xlabel=None, title=None, legend=None, show_hist=True, npoints=200, bins=30):
         | 
| 488 | 
            +
            		"""
         | 
| 489 | 
            +
            		Plot distribution.
         | 
| 490 | 
            +
             | 
| 491 | 
            +
            		.. code-block:: python
         | 
| 492 | 
            +
            			
         | 
| 493 | 
            +
            			import modusa as ms
         | 
| 494 | 
            +
            			import numpy as np
         | 
| 495 | 
            +
            			np.random.seed(42)
         | 
| 496 | 
            +
            			data = np.random.normal(loc=1, scale=1, size=1000)
         | 
| 497 | 
            +
            			ms.plot_dist(data, data+5, data-10, ann=[(0, 1, "A")], legend=("D1", "D2", "D3"), ylim=(0, 1), xlabel="X", ylabel="Counts", title="Distribution")
         | 
| 498 | 
            +
             | 
| 499 | 
            +
            		Parameters
         | 
| 500 | 
            +
            		----------
         | 
| 501 | 
            +
            		*args: ndarray
         | 
| 502 | 
            +
            			- Data arrays for which distribution needs to be plotted.
         | 
| 503 | 
            +
            			- Arrays will be flattened.
         | 
| 504 | 
            +
            		ann : list[tuple[Number, Number, str] | None
         | 
| 505 | 
            +
            			- A list of annotations to mark specific points. Each tuple should be of the form (start, end, label).
         | 
| 506 | 
            +
            			- Default: None => No annotation.
         | 
| 507 | 
            +
            		events : list[Number] | None
         | 
| 508 | 
            +
            			- A list of x-values where vertical lines (event markers) will be drawn.
         | 
| 509 | 
            +
            			- Default: None
         | 
| 510 | 
            +
            		xlim : tuple[Number, Number] | None
         | 
| 511 | 
            +
            			- Limits for the x-axis as (xmin, xmax).
         | 
| 512 | 
            +
            			- Default: None
         | 
| 513 | 
            +
            		ylim : tuple[Number, Number] | None
         | 
| 514 | 
            +
            			- Limits for the y-axis as (ymin, ymax).
         | 
| 515 | 
            +
            			- Default: None
         | 
| 516 | 
            +
            		xlabel : str | None
         | 
| 517 | 
            +
            			- Label for the x-axis.
         | 
| 518 | 
            +
            			- - Default: None
         | 
| 519 | 
            +
            		ylabel : str | None
         | 
| 520 | 
            +
            			- Label for the y-axis.
         | 
| 521 | 
            +
            			- Default: None
         | 
| 522 | 
            +
            		title : str | None
         | 
| 523 | 
            +
            			- Title of the plot.
         | 
| 524 | 
            +
            			- Default: None
         | 
| 525 | 
            +
            		legend : list[str] | None
         | 
| 526 | 
            +
            			- List of legend labels corresponding to each signal if plotting multiple distributions.
         | 
| 527 | 
            +
            			- Default: None
         | 
| 528 | 
            +
            		show_hist: bool
         | 
| 529 | 
            +
            			- Want to show histogram as well.
         | 
| 530 | 
            +
            		npoints: int
         | 
| 531 | 
            +
            			- Number of points for which gaussian needs to be computed between min and max.
         | 
| 532 | 
            +
            			- Higher value means more points are evaluated with the fitted gaussian, thereby higher resolution.
         | 
| 533 | 
            +
            		bins: int
         | 
| 534 | 
            +
            			- The number of bins for histogram.
         | 
| 535 | 
            +
            			- This is used only to plot the histogram.
         | 
| 536 | 
            +
             | 
| 537 | 
            +
            		Returns
         | 
| 538 | 
            +
            		-------
         | 
| 539 | 
            +
            		plt.Figure
         | 
| 540 | 
            +
            			- Matplotlib figure.
         | 
| 541 | 
            +
            		"""
         | 
| 542 | 
            +
            		from scipy.stats import gaussian_kde
         | 
| 543 | 
            +
            	
         | 
| 544 | 
            +
            		if isinstance(legend, str):
         | 
| 545 | 
            +
            			legend = (legend, )
         | 
| 546 | 
            +
            			
         | 
| 547 | 
            +
            		if legend is not None:
         | 
| 548 | 
            +
            			if len(legend) < len(args):
         | 
| 549 | 
            +
            				raise ValueError(f"Legend should be provided for each signal.")
         | 
| 550 | 
            +
            					
         | 
| 551 | 
            +
            		# Create figure
         | 
| 552 | 
            +
            		fig = plt.figure(figsize=(16, 4))
         | 
| 553 | 
            +
            		gs = gridspec.GridSpec(2, 1, height_ratios=[0.1, 1])
         | 
| 554 | 
            +
            	
         | 
| 555 | 
            +
            		colors = plt.get_cmap('tab10').colors
         | 
| 556 | 
            +
            	
         | 
| 557 | 
            +
            		dist_ax = fig.add_subplot(gs[1, 0])
         | 
| 558 | 
            +
            		annotation_ax = fig.add_subplot(gs[0, 0], sharex=dist_ax)
         | 
| 559 | 
            +
            	
         | 
| 560 | 
            +
            		# Set limits
         | 
| 561 | 
            +
            		if xlim is not None:
         | 
| 562 | 
            +
            			dist_ax.set_xlim(xlim)
         | 
| 563 | 
            +
            			
         | 
| 564 | 
            +
            		if ylim is not None:
         | 
| 565 | 
            +
            			dist_ax.set_ylim(ylim)
         | 
| 566 | 
            +
            			
         | 
| 567 | 
            +
            		# Add plot
         | 
| 568 | 
            +
            		for i, data in enumerate(args):
         | 
| 569 | 
            +
            			# Fit gaussian to the data
         | 
| 570 | 
            +
            			kde = gaussian_kde(data)
         | 
| 571 | 
            +
            		
         | 
| 572 | 
            +
            			# Create points to evaluate KDE
         | 
| 573 | 
            +
            			x = np.linspace(np.min(data), np.max(data), npoints)
         | 
| 574 | 
            +
            			y = kde(x)
         | 
| 575 | 
            +
            		
         | 
| 576 | 
            +
            			if legend is not None:
         | 
| 577 | 
            +
            				dist_ax.plot(x, y, color=colors[i], label=legend[i])
         | 
| 578 | 
            +
            				if show_hist is True:
         | 
| 579 | 
            +
            					dist_ax.hist(data, bins=bins, density=True, alpha=0.3, facecolor=colors[i], edgecolor='black', label=legend[i])
         | 
| 580 | 
            +
            			else:
         | 
| 581 | 
            +
            				dist_ax.plot(x, y, color=colors[i])
         | 
| 582 | 
            +
            				if show_hist is True:
         | 
| 583 | 
            +
            					dist_ax.hist(data, bins=bins, density=True, alpha=0.3, facecolor=colors[i], edgecolor='black')
         | 
| 584 | 
            +
            							
         | 
| 585 | 
            +
            		# Add annotations
         | 
| 586 | 
            +
            		if ann is not None:
         | 
| 587 | 
            +
            			annotation_ax.set_ylim(0, 1) # For consistent layout
         | 
| 588 | 
            +
            			# Determine visible x-range
         | 
| 589 | 
            +
            			x_view_min = xlim[0] if xlim is not None else np.min(x)
         | 
| 590 | 
            +
            			x_view_max = xlim[1] if xlim is not None else np.max(x)
         | 
| 591 | 
            +
            			for i, (start, end, tag) in enumerate(ann):
         | 
| 592 | 
            +
            				# We make sure that we only plot annotation that are within the x range of the current view
         | 
| 593 | 
            +
            				if start >= x_view_max or end <= x_view_min:
         | 
| 594 | 
            +
            					continue
         | 
| 595 | 
            +
            				
         | 
| 596 | 
            +
            				# Clip boundaries to xlim
         | 
| 597 | 
            +
            				start = max(start, x_view_min)
         | 
| 598 | 
            +
            				end = min(end, x_view_max)
         | 
| 599 | 
            +
            					
         | 
| 600 | 
            +
            				color = colors[i % len(colors)]
         | 
| 601 | 
            +
            				width = end - start
         | 
| 602 | 
            +
            				rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
         | 
| 603 | 
            +
            				annotation_ax.add_patch(rect)
         | 
| 604 | 
            +
            			
         | 
| 605 | 
            +
            				text_obj = annotation_ax.text((start + end) / 2, 0.5, tag, ha='center', va='center', fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True)
         | 
| 606 | 
            +
            				text_obj.set_clip_path(rect)
         | 
| 607 | 
            +
            					
         | 
| 608 | 
            +
            		# Add legend
         | 
| 609 | 
            +
            		if legend is not None:
         | 
| 610 | 
            +
            			handles, labels = dist_ax.get_legend_handles_labels()
         | 
| 611 | 
            +
            			fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.1), ncol=len(legend), frameon=True)
         | 
| 612 | 
            +
            			
         | 
| 613 | 
            +
            		# Set title, labels
         | 
| 614 | 
            +
            		if title is not None:
         | 
| 615 | 
            +
            			annotation_ax.set_title(title, pad=10, size=11)
         | 
| 616 | 
            +
            		if xlabel is not None:
         | 
| 617 | 
            +
            			dist_ax.set_xlabel(xlabel)
         | 
| 618 | 
            +
            		if ylabel is not None:
         | 
| 619 | 
            +
            			dist_ax.set_ylabel(ylabel)
         | 
| 620 | 
            +
            			
         | 
| 621 | 
            +
            		# Remove the boundaries and ticks from annotation axis
         | 
| 622 | 
            +
            		if ann is not None:
         | 
| 623 | 
            +
            			annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
         | 
| 624 | 
            +
            		else:
         | 
| 625 | 
            +
            			annotation_ax.axis("off")
         | 
| 626 | 
            +
            			
         | 
| 627 | 
            +
            		fig.subplots_adjust(hspace=0.01, wspace=0.05)
         | 
| 628 | 
            +
            		plt.close()
         | 
| 629 | 
            +
            		return fig
         |