commit a9da0c956520e4ea19ed0948bbf3129e98ae0c4b
parent 6d356459171a6e195d97d1018dfdd899739e9813
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date: Mon, 15 Dec 2025 14:06:32 -0300
add `guias/*`
Diffstat:
| A | guias/2_19.py | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | guias/7_12.py | | | 27 | +++++++++++++++++++++++++++ |
| A | guias/7_17.py | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
| A | guias/7_20.py | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | guias/7_22.py | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | guias/8_07.py | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | guias/8_10.py | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | guias/utils.py | | | 541 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
8 files changed, 851 insertions(+), 0 deletions(-)
diff --git a/guias/2_19.py b/guias/2_19.py
@@ -0,0 +1,45 @@
+# 2_19.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+# `a` coeficientes de la entrada
+# `b` coeficientes de las salida
+def respuesta_impulso(a, b, N):
+ a = np.array(a)
+ b = np.array(b)
+
+ # delta para la entrada
+ x = np.zeros(N)
+ x[0] = 1
+
+ y = np.zeros(N)
+
+ for n in range(N):
+ # entrada
+ parte_x = 0
+ for i in range(len(a)):
+ # condicion de reposo inicial
+ if n - i >= 0:
+ parte_x += a[i] * x[n - i]
+
+ # salida
+ parte_y = 0
+ for j in range(1, len(b)):
+ # condicion de reposo inicial
+ if n - j >= 0:
+ parte_y += b[j] * y[n - j]
+
+ # normalizado por a0 (por si es distinto de 1)
+ y[n] = (parte_x - parte_y) / a[0]
+
+ return y
+
+# `y(n) - 0.25*y(n - 1) = x(n)` coeficientes: a = [1, -0.5] y b = [1]
+a = [1]
+b = [1, -0.25]
+N = 5
+
+h = respuesta_impulso(a, b, N)
+print(h) # [1. 0.25 0.0625 0.015625 0.00390625]
diff --git a/guias/7_12.py b/guias/7_12.py
@@ -0,0 +1,27 @@
+# 7_12.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+w = np.linspace(-np.pi, np.pi, 1000)
+h = 1 - np.exp(-1j * 5 * w)
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))
+
+# magnitud
+ax1.plot(w, np.abs(h))
+ax1.set_title(r'Magnitud $|1 - e^{-j5\omega}|$')
+ax1.set_ylabel('Amplitud')
+ax1.set_xticks([-np.pi, -2*np.pi/5, 0, 2*np.pi/5, np.pi])
+ax1.set_xticklabels([r'$-\pi$', r'$-2\pi/5$', '0', r'$2\pi/5$', r'$\pi$'])
+ax1.grid(True)
+
+# fase
+ax2.plot(w, np.angle(h))
+ax2.set_title(r'Fase $\angle H(e^{j\omega})$')
+ax2.set_ylabel('Fase [rad]')
+ax2.set_xlabel('Frecuencia [rad/muestra]')
+ax2.grid(True)
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/7_17.py b/guias/7_17.py
@@ -0,0 +1,38 @@
+# 7_17.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import fft, signal
+
+T = 1/100 # seg
+sample_rate = 1/T
+sample_time = 1
+N = int(sample_rate*sample_time)
+
+for f1 in [30, 30.5]:
+ for Nf in [N, 10*N]:
+ for alpha in [0.5, 1, 5]:
+ f2 = f1 + alpha/(N*T)
+
+ # la frecuencia de muestreo de la señal es fija
+ t = np.linspace(0, sample_time, int(N))
+ y = np.cos(2*np.pi*f1*t) + np.cos(2*np.pi*f2*t)
+
+ print(f1, f2)
+
+ # calculo de DFT de Nf puntos
+ Xf = np.fft.fft(t, n=Nf)
+ freqs = np.fft.fftfreq(Nf, d=T)
+
+ # centrar la frecuencia 0
+ Xf = np.fft.fftshift(Xf)
+ freqs = np.fft.fftshift(freqs)
+
+ # grafico
+ plt.figure(figsize=(8, 4))
+ markerline, stemlines, baseline = plt.stem(freqs, np.abs(Xf))
+ plt.setp(baseline, color='black', linewidth=0)
+ plt.title(f"DFT con Nf={Nf}, f1={f1}, f2={f2:.2f}")
+ plt.grid(True, linestyle=':', alpha=0.7)
+ plt.grid(True, which='minor')
+ plt.show()
diff --git a/guias/7_20.py b/guias/7_20.py
@@ -0,0 +1,34 @@
+# 7_20.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+fs = 400
+T = 1/fs
+t = np.arange(0, 1, T)
+
+x_a = np.cos(2*np.pi*100*t)
+x_b = (1 + np.cos(2*np.pi*10*t)) * np.cos(2*np.pi*100*t)
+x_c = np.cos(2 * np.pi * 100 * t**2)
+
+sig = [x_a, x_b, x_c]
+
+plt.figure(figsize=(15, 5))
+
+for i, x in enumerate(sig):
+ plt.subplot(1, 3, i+1)
+
+ # `nperseg` tamaño de la ventana
+ # `noverlap` solapamiento entre ventanas
+ f, times, Sxx = signal.spectrogram(x, fs, window='hann',
+ nperseg=128, noverlap=32)
+
+ plt.pcolormesh(times, f, 10*np.log10(Sxx), cmap='inferno', shading='gouraud')
+
+ if i == 0:
+ plt.ylabel('Frecuencia [Hz]')
+ plt.xlabel('Tiempo [s]')
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/7_22.py b/guias/7_22.py
@@ -0,0 +1,34 @@
+# 7_22.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+fs = 400
+T = 1/fs
+t = np.arange(0, 1, T)
+
+x_a = np.cos(2*np.pi*100*t)
+x_b = (1 + np.cos(2*np.pi*10*t)) * np.cos(2*np.pi*100*t)
+x_c = np.cos(2 * np.pi * 100 * t**2)
+
+sig = [x_a, x_b, x_c]
+
+plt.figure(figsize=(15, 5))
+
+for i, x in enumerate(sig):
+ plt.subplot(1, 3, i+1)
+
+ # nperseg: tamaño de la ventana
+ # noverlap: solapamiento entre ventanas
+ f, times, Sxx = signal.spectrogram(x, fs, window='hann',
+ nperseg=128, noverlap=32)
+
+ plt.pcolormesh(times, f, 10*np.log10(Sxx), cmap='inferno', shading='gouraud')
+
+ if i == 0:
+ plt.ylabel('Frecuencia [Hz]')
+ plt.xlabel('Tiempo [s]')
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/8_07.py b/guias/8_07.py
@@ -0,0 +1,66 @@
+# 8_07.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from numpy import pi, sin
+
+tau = 0.1
+
+T = 2 * pi # Periodo
+w0 = 2 * pi / T
+D = 0.5 # duty cycle
+
+k_max = 100
+ks = np.arange(k_max, dtype=int)
+a_k = np.arange(k_max, dtype=float)
+b_k = np.arange(k_max, dtype=float)
+
+for k in range(k_max):
+ if k == 0:
+ a_k[k] = (w0*D*T) / (2*pi)
+ b_k[k] = (1/((tau*2*pi/T)+1)) * (w0*D*T)/(2*pi)
+ else:
+ a_k[k] = sin(k*D*w0*T/2) / (k*pi)
+ b_k[k] = (1/((tau*2*pi/T)+1)) * (sin(k*D*w0*T/2)/(k*pi))
+
+# magnitud a_k
+plt.subplot(1, 2, 1)
+markerline, stemlines, _ = plt.stem(ks, np.abs(a_k), basefmt=" ")
+plt.setp(markerline, 'markerfacecolor', 'blue')
+plt.title('Espectro de Magnitud $|a_k|$')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Amplitud')
+plt.grid(True, linestyle='--')
+
+# fase a_k
+plt.subplot(1, 2, 2)
+plt.stem(ks, np.angle(a_k), basefmt=" ")
+plt.title('Espectro de Fase $\\angle a_k$ (rad)')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Fase')
+plt.grid(True, linestyle='--')
+
+plt.tight_layout()
+plt.show()
+
+plt.figure(figsize=(10, 10))
+
+# magnitud b_k
+plt.subplot(1, 2, 1)
+markerline, stemlines, _ = plt.stem(ks, np.abs(b_k), basefmt=" ")
+plt.setp(markerline, 'markerfacecolor', 'blue')
+plt.title('Espectro de Magnitud $|b_k|$')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Amplitud')
+plt.grid(True, linestyle='--')
+
+# fase b_k
+plt.subplot(1, 2, 2)
+plt.stem(ks, np.angle(b_k), basefmt=" ")
+plt.title('Espectro de Fase $\\angle b_k$ (rad)')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Fase')
+plt.grid(True, linestyle='--')
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/8_10.py b/guias/8_10.py
@@ -0,0 +1,66 @@
+# 8_10.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+# H(z) = (z) / (z - 0.5) => H(z) = 1 / (1 - 0.5z^-1)
+zeros = [0]
+poles = [0.5]
+k = 1
+
+# zeros = [0, 1]
+# poles = [0.5, 1.5]
+# k = 1
+
+system_zpk = signal.dlti(zeros, poles, k, dt=1.0)
+
+n_points = 100
+n_1, y_1 = signal.dimpulse(system_zpk, n=n_points)
+n_2, y_2 = signal.dstep(system_zpk, n=n_points)
+
+# la salida de dimpulse es una lista, tomamos el primer elemento
+h_n = y_1[0].flatten()
+y_n = y_2[0].flatten()
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10))
+
+# respuesta al impulso
+markerline, stemlines, baseline = ax1.stem(n_1, h_n, basefmt=" ")
+plt.setp(baseline, color='black', linewidth=0)
+ax1.set_title('Respuesta al Impulso $h[n]$')
+ax1.set_ylabel('Amplitud')
+ax1.grid(True, linestyle='--', alpha=0.7)
+
+# respuesta al escalon
+markerline, stemlines, baseline = ax2.stem(n_2, y_n, basefmt=" ")
+plt.setp(baseline, color='black', linewidth=0)
+ax2.set_title('Respuesta al Escalón $y[n]$')
+ax2.set_xlabel('Muestra ($n$)')
+ax2.set_ylabel('Amplitud')
+ax2.grid(True, linestyle='--', alpha=0.7)
+
+plt.setp(baseline, color='black', linewidth=1)
+plt.tight_layout()
+plt.show()
+
+# transferencia como diagrama de bode (modulo/fase)
+w, h = signal.dfreqresp(system_zpk)
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10))
+
+# magnitud
+ax1.plot(w, 20 * np.log10(abs(h)))
+ax1.set_title('Respuesta en Frecuencia (Magnitud)')
+ax1.set_ylabel('Amplitud [dB]')
+ax1.grid(True)
+
+# fase
+ax2.plot(w, np.angle(h))
+ax2.set_title('Respuesta en Frecuencia (Fase)')
+ax2.set_ylabel('Fase [rad]')
+ax2.set_xlabel('Frecuencia [rad/muestra]')
+ax2.grid(True)
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/utils.py b/guias/utils.py
@@ -0,0 +1,541 @@
+import matplotlib.pyplot as plt
+from matplotlib.ticker import AutoMinorLocator
+from matplotlib.ticker import MultipleLocator
+from matplotlib.ticker import MaxNLocator
+from matplotlib.ticker import FuncFormatter
+from cycler import cycler
+
+import matplotlib
+import numpy as np
+import os
+
+from scipy.io import wavfile
+from scipy.fft import fft, ifft, fftfreq
+from scipy.signal import spectrogram
+
+plot_dir = "."
+
+matplotlib.rcParams['font.family'] = 'Inter'
+matplotlib.rcParams['font.size'] = 12
+matplotlib.rcParams['axes.prop_cycle'] = cycler(
+ color=['#1f77b4', '#ff0000', '#ff5f1f', 'green'])
+matplotlib.use("TkAgg")
+
+def ticks_label_format(x, pos):
+ # 3 decimales, se eliminan los ceros y puntos
+ return f"{x:.3f}".rstrip("0").rstrip(".")
+
+def time_graph_multiple_data(x, y_arr, y_lab, t=0, dt=0, a=0, da=0, show=True):
+ figure, axis = plt.subplots(figsize=(8, 4))
+
+ for i, y in enumerate(y_arr):
+ axis.plot(x, y, label=y_lab[i], alpha=0.75)
+
+ axis.set(xlabel='Tiempo [s]', ylabel='Amplitud normalizada')
+
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+ axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ plt.tight_layout()
+
+ # max 3 decimales
+ axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+
+ axis.set_xlim([t, t+dt if dt > 0 else x[-1]])
+ axis.set_ylim(-1.1, 1.1)
+
+ # resaltado de parte de la señal (solo si a != 0)
+ axis.axvspan(a, a+da, color='skyblue',
+ alpha=0 if a == 0 else 0.50,
+ label=f"Un periodo T={da}s" if da != 0 else "")
+ axis.legend(loc='upper left')
+
+ if show:
+ plt.show()
+
+ return figure, axis
+
+# todos deben la misma cantidad de elementos que el primero
+def time_plot_multiple(fs, data_arr, leg_arr, save_name="", t=0, dt=0, a=0, da=0, show=False):
+ x = np.arange(len(data_arr[0])) / fs
+ fig, ax = time_graph_multiple_data(x, data_arr, leg_arr, t, dt, a=a, da=da, show=show)
+
+ if show == False:
+ save_plot(fig, save_name)
+
+ return fig, ax
+
+def time_graph_data(x, y, t=0, dt=0, a=0, da=0, ylim=[], show=True):
+ figure, axis = plt.subplots(figsize=(8, 4))
+
+ axis.plot(x, y, label='Señal de audio')
+ axis.set(xlabel='Tiempo [s]', ylabel='Amplitud normalizada')
+
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+ axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ plt.tight_layout()
+
+ # max 3 decimales
+ axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+
+ axis.set_xlim(t, t+dt if dt > 0 else x[-1])
+
+ if len(ylim) != 0:
+ axis.set_ylim(ylim[0], ylim[1])
+ else:
+ axis.set_ylim(-1.1, 1.1)
+
+ # resaltado de parte de la señal (solo si a != 0)
+ axis.axvspan(a, a+da, color='skyblue',
+ alpha=0 if a == 0 else 0.50,
+ label=f"Un periodo T={da}s" if da != 0 else "")
+ axis.legend(loc='upper left')
+
+ if show:
+ plt.show()
+
+ return figure, axis
+
+def normalize(data):
+ data = data.astype(np.float32)
+ data /= np.max(np.abs(data))
+ return data
+
+def time_plot(fs, data, save_name="", t=0, dt=0, a=0, da=0):
+ show = True if save_name == "" else False
+
+ # normaliza la amplitud dividiendo por el valor maximo del tipo de dato
+ data = normalize(data)
+
+ x = np.arange(len(data)) / fs
+ fig, ax = time_graph_data(x, data, t, dt, a, da, show)
+
+ if show == False:
+ save_plot(fig, save_name)
+
+ return fig, ax
+
+def save_plot(fig, name, save_dir="", overwrite=True):
+ base_name = os.path.basename(name)
+ file_name, ext = os.path.splitext(base_name.replace('.', '_'))
+
+ if save_dir == "":
+ save_dir = plot_dir
+
+ if save_dir != "" and not save_dir.endswith('/'):
+ save_dir += "/"
+
+ file_path_no_ext = f'{save_dir}{file_name}'
+ save_name = f'{file_path_no_ext}.png'
+
+ if overwrite == False:
+ if os.path.exists(save_name):
+ i = 1
+ while True:
+ new_save_name = f'{file_path_no_ext}_{i:02d}'
+ if not os.path.exists(f'{new_save_name}.png'):
+ save_name = f'{new_save_name}.png'
+ break
+ i += 1
+
+ print(f'[LOG] Guardando figura `{save_name}`')
+
+ # crea carpeta para plots
+ os.makedirs(plot_dir, exist_ok=True)
+ fig.savefig(save_name, dpi=100, bbox_inches="tight")
+ plt.close(fig) # liberar memoria
+
+def save_to_wav(fs, data, save_name):
+ # normalizar para prevenir clipping
+ data = data / np.max(np.abs(data))
+
+ # convertir a 16-bit PCM para WAV
+ data_as_int16 = np.int16(data * 32767)
+
+ # crea carpeta para wavs
+ os.makedirs(out_dir, exist_ok=True)
+
+ file_path = f'{out_dir}{save_name}'
+ print(f'[LOG] Guardando audio: `{file_path}')
+ wavfile.write(file_path, fs, data_as_int16)
+
+# frecuencia
+
+# data = [[fft], [freqs], [legends]]
+def freq_graph_multiple_data(data, f_min=0, f_max=0, y_min=0, y_max=0, show=True):
+ fig, axis = plt.subplots(figsize=(8, 4))
+
+ for i, (fft, freqs, label) in enumerate(data):
+ # print(label)
+ N = len(freqs)
+ x = freqs[:N // 2]
+ y = np.abs(fft[:N // 2])
+ axis.plot(x, y, label=label, alpha=0.90, linewidth=((len(data)-i-1)*0.5 + 1.5))
+
+ axis.set(xlabel='Frecuencia [Hz]', ylabel='Magnitud [dB]')
+
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+ axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ plt.tight_layout()
+
+ # max 3 decimales
+ axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+ plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
+
+ axis.set_xlim([f_min, f_max if f_max != 0 else 20000])
+ axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+ axis.legend(loc='upper right')
+
+ if show:
+ plt.show()
+
+ return fig, axis
+
+def freq_graph_data_norm(x, y, x_min=0, x_max=0, y_min=0, y_max=0, show=True,
+ xlabel="", ylabel=""):
+
+ fig, axis = plt.subplots(figsize=(8, 4))
+
+ axis.plot(x, y)
+
+ if xlabel == "":
+ axis.set(xlabel='Frecuencia normalizada')
+
+ if ylabel == "":
+ axis.set(ylabel='Magnitud [dB]')
+
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+ axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ plt.tight_layout()
+
+ axis.set_xlim([x_min, x_max if x_max != 0 else 1])
+ axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+ if show:
+ plt.show()
+
+ return fig, axis
+
+def freq_graph_multiple_data_norm(x, y_arr, labels,
+ x_min=0, x_max=0, y_min=0, y_max=0,
+ show=True, xlabel="", ylabel=""):
+
+ fig, axis = plt.subplots(figsize=(8, 4))
+
+ for i, y in enumerate(y_arr):
+ axis.plot(x, y, label=labels[i], alpha=0.75)
+
+ if xlabel == "":
+ axis.set(xlabel='Frecuencia normalizada')
+
+ if ylabel == "":
+ axis.set(ylabel='Magnitud [dB]')
+
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+ axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ plt.tight_layout()
+ axis.legend(loc='upper right')
+
+ axis.set_xlim([x_min, x_max if x_max != 0 else 1])
+ axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+ if show:
+ plt.show()
+
+ return fig, axis
+
+
+def freq_graph_data(x, y, f_min=0, f_max=0, y_min=0, y_max=0, show=True,
+ xlabel="", ylabel=""):
+ fig, axis = plt.subplots(figsize=(8, 4))
+
+ axis.plot(x, y)
+
+ if xlabel == "":
+ axis.set(xlabel='Frecuencia [Hz]')
+
+ if ylabel == "":
+ axis.set(ylabel='Magnitud')
+
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+ axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ plt.tight_layout()
+
+ # max 3 decimales
+ axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+ plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
+
+ axis.set_xlim([f_min, f_max if f_max != 0 else 20000])
+ axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+ if show:
+ plt.show()
+
+ return fig, axis
+
+def freq_compute_fft(fs, data, t=0, dt=0, N=0):
+ i = 0
+ di = fs*len(data)
+ if t != 0 or dt != 0:
+ i = int(t*fs)
+ di = int((t+dt)*fs)
+
+ interval_data = data[i:di]
+
+ # puntos de la fft
+ if N == 0:
+ N = len(interval_data)*4
+
+ interval_fft = fft(interval_data, N)
+ interval_freqs = fftfreq(N, d=1/fs)
+
+ return interval_fft, interval_freqs
+
+# hace la transformacion a frecuencias y pasa lo transformado a `freq_graph_data`
+def freq_plot(fs, data, f_min=0, f_max=0, y_min=0, y_max=0,
+ t=0, dt=0, a=0, da=0, show=False, N=0, save_name="", save_dir=""):
+
+ interval_fft, interval_freqs = freq_compute_fft(fs, data, t, dt, N=N)
+ N = len(interval_fft)
+
+ # se toma la parte positiva en ambos casos (primer parte del arreglo)
+ x = interval_freqs[:N // 2]
+ y = np.abs(interval_fft[:N // 2])
+
+ fig, ax = freq_graph_data(x, y, f_min, f_max, y_min, y_max, show=show)
+
+ if save_name != "":
+ save_plot(fig, name=save_name, save_dir=save_dir)
+
+ return fig, ax
+
+# frecuencia de muestreo comun
+# computa y grafica en una figura la fft the los datos en `data_arr`
+def freq_plot_multiple(fs, data_arr, leg_arr, save_name="",
+ f_min=0, f_max=0, y_min=0, y_max=0, t=0, dt=0, show=True):
+
+ fft_freqs_arr = []
+ for i, data in enumerate(data_arr):
+ fft, freqs = freq_compute_fft(fs, data, t, dt)
+ fft_freqs_arr.append([fft, freqs, leg_arr[i]])
+
+ fig, axis = freq_graph_multiple_data(fft_freqs_arr, f_min, f_max, y_min, y_max, show)
+
+ if save_name != "":
+ save_plot(fig, save_name)
+
+def generate_spectrogram(fs, data, t=0, dt=0, N=1024, overlp=16, win='hamm'):
+ if dt == 0:
+ dt = (len(data)/fs)-t
+
+ i = int(t*fs)
+ di = int((t+dt)*fs)
+ interval_data = data[i:di]
+
+ # `nperseg` tamaño de ventana (número de muestras por segmento)
+ # `noverlap` cantidad de solapamiento entre ventanas
+ f, time, Sxx = spectrogram(interval_data, fs=fs, nperseg=N, noverlap=overlp,
+ window=win)
+
+ return f, time, Sxx
+
+def spectrogram_plot(fs, data, save_name="", save_dir="", t=0, dt=0, N=1024,
+ overlp=16, win='hamm', xlim=[], ylim=[],
+ shading='gouraud', cmap='viridis', show=False):
+
+ # f, time, Sxx = generate_spectrogram(fs, data, t, dt, N, overlp, win)
+ f, time, Sxx = spectrogram(data, fs=fs, nperseg=N, noverlap=overlp,
+ window=win)
+ fig, axis = plt.subplots(figsize=(8, 4))
+
+ # plt.pcolormesh(time, f, Sxx**0.5)
+ plt.pcolormesh(time, f, 10*np.log10(Sxx), shading=shading, cmap=cmap)
+
+ plt.ylabel('Frecuencia [Hz]')
+ plt.xlabel('Tiempo [s]')
+
+ if len(xlim) != 0:
+ plt.xlim(xlim)
+
+ if len(ylim) != 0:
+ plt.ylim(ylim)
+ else:
+ plt.ylim(1, 20000)
+
+ if show == True:
+ plt.show()
+ else:
+ if save_name != "":
+ save_plot(fig, name=save_name, save_dir=save_dir, overwrite=True)
+
+ return fig, axis
+
+def bode_plot(w, H, show=True):
+ figure, axis = plt.subplots(figsize=(8, 4))
+
+ axis.plot(w, 20*np.log10(np.abs(H)))
+
+ axis.set(xlabel='Frecuencia [Hz]', ylabel='Magnitud [dB]')
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ plt.tight_layout()
+
+ axis.set_xlim(0.0, 20e3)
+
+ axis.legend(loc='upper left')
+
+ if show:
+ plt.show()
+
+ return figure, axis
+
+def freq_response_plot(w, H, phase, show=True, fc=20e3):
+ fig, ax1 = plt.subplots(figsize=(8, 4))
+
+ H_db = 20*np.log10(np.abs(H))
+
+ line1, = ax1.plot(w, H_db)
+ ax1.set(xlabel='Frecuencia [Hz]', ylabel='Magnitud [dB]')
+ ax1.minorticks_on()
+ ax1.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ ax1.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ ax1.set_xlim(0.0, fc)
+
+ ax2 = ax1.twinx()
+ line2, = ax2.plot(w, phase, color="tab:red")
+ ax2.set_ylabel("Fase [grados]", color="black")
+ ax2.tick_params(axis='y', labelcolor="black")
+
+ # axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ ax1.yaxis.set_minor_locator(AutoMinorLocator(2))
+
+ # Add ONE point
+
+ # Find index closest to -3 dB
+ idx = np.argmin(np.abs(H_db + 3)) # H_db = -3 => H_db +3 = 0
+ w_3db = w[idx]
+ H_3db = H_db[idx]
+ line3 = ax1.scatter(w_3db, H_3db, color='tab:green', s=50, zorder=10)
+
+ # nyquist
+ w_nyquist = 2756.25
+ idx = np.argmin(np.abs(w - w_nyquist)) # H_db = -3 => H_db +3 = 0
+ H_nyquist = H_db[idx]
+ line4 = ax1.scatter(w_nyquist, H_nyquist, color='tab:orange', s=50, zorder=10)
+
+ ax1.legend([line1, line2, line3, line4],
+ ["Magnitud [dB]",
+ "Fase [grados]",
+ r'-3dB $\approx$ %0.0f Hz'%w_3db,
+ r'Nyquist $\approx$ %0.0f Hz'%w_nyquist],
+ loc='upper right')
+
+ plt.tight_layout()
+
+ if show:
+ plt.show()
+
+ return fig, ax1, ax2
+
+def dtime_plot(N, f, save_name="", legend="", n=0, dn=0, a=0, da=0):
+ show = True if save_name == "" else False
+
+ n = np.arange(N + 1)
+
+ fig, axis = plt.subplots(figsize=(8,4))
+ axis.set(xlabel='Tiempo discreto', ylabel='Amplitud')
+
+ markerline, stemlines, baseline = axis.stem(
+ n, f,
+ markerfmt='o', # tipo de marcador en la cabeza
+ basefmt="k-",
+ )
+
+ markerline.set_markersize(2.0)
+ stemlines.set_linewidth(0.35)
+ baseline.set_linewidth(0.5)
+
+ stemlines.set_zorder(2)
+ markerline.set_zorder(3)
+ baseline.set_zorder(1)
+
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ axis.set_xlim(0, N+1)
+ axis.set_ylim(-0.03, 0.15)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=15))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(2))
+
+ axis.yaxis.set_minor_locator(AutoMinorLocator(2))
+
+ # axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ # axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ if legend != "":
+ axis.legend([markerline], [legend], loc='upper right')
+
+ if show == False:
+ save_plot(fig, save_name)
+ else:
+ plt.show()
+
+ return fig, axis
+
+# np.linspace(start, stop, num).astype(int)