From 67d24118e84b446dcdf05868cd3b3f60b740fbc1 Mon Sep 17 00:00:00 2001 From: Philipp Niedermayer <p.niedermayer@gsi.de> Date: Wed, 18 May 2022 11:50:52 +0200 Subject: [PATCH] Plot excitation ranges --- plotting.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/plotting.py b/plotting.py index 7d130c1..756febf 100644 --- a/plotting.py +++ b/plotting.py @@ -63,6 +63,21 @@ def add_resonance_vlines(axes, max_order, color='r'): add_vline(axes, to_half_intervall(r), color, text=f'{n}/{m} resonance' if m > 1 else None, lw=3/max(1, m-2)**.5, alpha=1/max(1, m-2), text_vertical=True, text_top=True) +def subplot_shared_labels(axes, xlabel=None, ylabel=None, clear=True): + """Adds labels to shared axes as needed + + :param axes: 2D array of axes from subplots (pass squeeze=False to plt.subplots if required) + :param xlabel: the shared xlabel + :param ylabel: the shared ylabel + :param clear: if true (default) any existing labels on the axes will get cleared + """ + for r in range(axes.shape[0]): + for c in range(axes.shape[1]): + if clear: axes[r,c].set(xlabel=None, ylabel=None) + if c == 0 or not axes[r,c].get_shared_y_axes().joined(axes[r,c], axes[r,0]): + axes[r,c].set(ylabel=ylabel) + if r == axes.shape[0]-1 or not axes[r,c].get_shared_x_axes().joined(axes[r,c], axes[-1,c]): + axes[r,c].set(xlabel=xlabel) @@ -136,21 +151,31 @@ def plot_tune_spectrum(ax, libera_data, xy, turn_range=None, time_range=None, tu print('Warning: fit failed') else: q = SI.Measurement(fitr[0][2], (fitr[1][2]**2+fitr[0][3]**2+fitr[1][3]**2)**0.5, '') # conservative estimate of error including width of peak - ax.plot(*fitr[-1], '--', label=f'Fit $q_{xy}={q:~L}$') + ax.plot(*fitr[-1], '--', label=f'Fit $q_{xy}={q:~L}$', zorder=50) + -def plot_tune_spectrogram(ax, libera_data, xy, nperseg=2**12, noverlap=None, ninterpol=4, smoothing=0, over_time=True, colorbar=False, - turn_range=None, time_range=None, tune_range=None): +def plot_tune_spectrogram(ax, libera_data, xy, nperseg=2**12, noverlap=None, ninterpol=4, over_time=True, colorbar=False, + turn_range=None, time_range=None, tune_range=None, excitation=None, + cmap='gist_heat_r'): """Plot a tune spectrogram based on turn-by-turn data :param ax: Axis to plot onto :param libera_data: Instance of LiberaTBTData class :param xy: either 'x' or 'y' + :param over_time: plot data as function of time (if true) or turn number (otherwise). + Note that plotting over time is not suited for interactive plots. + :param colorbar: include colorbar :param turn_range: tuple of (start_turn, stop_turn) for range to plot - :param turn_range: tuple of (start_time, stop_time) in seconds for range to plot + :param time_range: tuple of (start_time, stop_time) in seconds for range to plot + :param tune_range: tuple of (start_tune, stop_tune) ror range to plot + :param excitation: tuple of (type, tune, ...) to indicate excitation range. Supported types are: + Excitation frequency band: ('band', tune, bandwidth_in_Hz) + Sinusoidal excitation: ('sine', tune) + Chirp excitation: ('chirp', tune, bandwidth_in_Hz, chirp_frequency_in_Hz, chirp_phase_in_rad) """ noverlap = (nperseg - nperseg//ninterpol) if noverlap is None else noverlap @@ -162,9 +187,12 @@ def plot_tune_spectrogram(ax, libera_data, xy, nperseg=2**12, noverlap=None, nin tune, turn, value = scipy.signal.stft(tbt_data, fs=1, nperseg=nperseg, noverlap=noverlap, window='boxcar', boundary=None, padded=False) time = libera_data.t[turn_range][turn.astype(int)] + frev = scipy.ndimage.uniform_filter1d(1/np.diff(libera_data.t)[turn_range], size=nperseg)[turn.astype(int)] mag = np.abs(value) #mag[0,:] = 0 # supress DC + xdata = time if over_time else turn + if tune_range is not None: mask = irng(tune, *tune_range) tune, mag = tune[mask], mag[mask, :] @@ -172,8 +200,9 @@ def plot_tune_spectrogram(ax, libera_data, xy, nperseg=2**12, noverlap=None, nin tune_range = (0, 0.5) if over_time: - cm = ax.pcolormesh(time, tune, mag, shading='nearest', - cmap='gist_heat_r', + # pcolormesh can handle non-equidistant data, but is not well suited for interactive plots (slow!) + cm = ax.pcolormesh(xdata, tune, mag, shading='nearest', cmap=cmap, + #cmap='gist_heat_r', vmin=np.percentile(mag, 0.5), vmax=np.percentile(mag, 99.5), #cmap='plasma_r', #norm=mpl.colors.LogNorm(), @@ -181,12 +210,33 @@ def plot_tune_spectrogram(ax, libera_data, xy, nperseg=2**12, noverlap=None, nin #vmin=np.nanmean(mag), vmax=np.nanmean(mag)+np.nanstd(mag), ) else: - cm = ax.imshow(mag, extent=(turn[0], turn[-1], tune[-1], tune[0]), aspect='auto', rasterized=True, - cmap='plasma_r', + # imshow is much faster, but can only handle equidistant data + cm = ax.imshow(mag, extent=(xdata[0], xdata[-1], tune[-1], tune[0]), aspect='auto', rasterized=True, + cmap=cmap, #cmap='plasma_r', vmin=np.percentile(mag, 1), vmax=np.percentile(mag, 99), ) if colorbar: fig.colorbar(cm, label='FFT magnitude', ax=ax) + + if excitation is not None: + ex_type, ex_q, *ex_args = excitation + ex_style = dict(lw=1, ls=':', c='k', alpha=0.5, zorder=50) + if ex_type == 'band': + ex_dq = ex_args[0]/frev + ax.plot(xdata, ex_q+ex_dq, xdata, ex_q-ex_dq, **ex_style) + elif ex_type == 'sine': + ax.plot(xdata, ex_q*np.ones_like(xdata), **ex_style) + elif ex_type == 'chirp': + ex_dq = ex_args[0]/frev + ex_fc, ex_pc = ex_args[1:3] + ax.plot(xdata, ex_q + ex_dq/2*np.sin(2*np.pi*ex_fc*time + ex_pc), **ex_style) + else: + raise NotImpementedError(f'Excitation type {ex_type} not implemented') + + + + ax.set(ylim=tune_range, ylabel=f'Tune $q_{xy}$') # or $1-q_{xy}$') - return time if over_time else turn, tune, mag + return xdata, tune, mag, frev + -- GitLab