Skip to content
Snippets Groups Projects
profile.py 3.48 KiB
Newer Older
#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" Methos for making beam profile realted plots, e.g. from ionisation profile monitor (IPM) data

"""

__author__ = "Philipp Niedermayer"
__contact__ = "p.niedermayer@gsi.de"
__date__ = "2022-09-29"



from itertools import cycle

from .common import *






def plot_beam_size(ax, ipm_data, xy, time_range=None, smoothing=None, fit=fit_lorenzian, **kwargs):
    """Plot beam size as function of time from fitted IPM data
    
    :param ipm_data: instance of IPMData
    :param xy: plane to consider, either 'x' or 'y'
    :param time_range: range of (start, stop) time in s to plot
    :param smoothing: if specified, apply a moving average smoothing filter of this width to the data
    :param fit: fit function
    """
    mask = slice(None, None) if time_range is None else irng(ipm_data.t, *time_range)
    data = getattr(ipm_data, xy)[mask]    
    
    #if smoothing: data = scipy.signal.savgol_filter(data, smoothing, 0, axis=0)
    
    n = data.shape[0]
    v, ve = np.nan*np.zeros(n), np.nan*np.zeros(n)
    for j in range(n):
        try:
            (*_, v[j]), (*_, ve[j]), _, fitted = fit(ipm_data.w, data[j,:])
        except RuntimeError: # fit failed
            ...
    
    if smoothing:
        v, ve = [scipy.signal.savgol_filter(_, smoothing, 0) for _ in (v, ve)] # savgol filter with order 0 is moving average
    
    ls, = ax.plot(ipm_data.t[mask], v, **kwargs)
    ax.fill_between(ipm_data.t[mask], v-ve, v+ve, alpha=0.3, color=ls.get_color(), zorder=-10)    
    ax.set(ylabel=f'Beam size $\\sigma_{xy}$ / mm', xlabel='Time / s')



Philipp Niedermayer's avatar
Philipp Niedermayer committed
def plot_beam_profile(ax, ipm_data, xy, at_time, smoothing=None, fit=fit_gaussian, cut_lower=None, **kwargs):
    """Plot IPM beam profile at given time with fits
    
    :param ax: The axis to plot onto
    :param ipm_data: data object with IPM profile data
    :param xy: what to plot, 'x' or 'y'
    :param at_time: time in sec of profile to plot
    :param smoothing: smooth profile over every this many bins
    :param fit: function or list of functions to use for fitting, see fitting.py
    """
Philipp Niedermayer's avatar
Philipp Niedermayer committed
    profile = np.copy(getattr(ipm_data, xy)[np.argmin(np.abs(ipm_data.t-at_time)),:]) # copy in order not to modify in-place
    if smoothing: profile = smooth(profile, n=smoothing)[0]
Philipp Niedermayer's avatar
Philipp Niedermayer committed
    
    # cut MCP noise away
    if cut_lower:
        ax.stairs(profile[1:-1], (ipm_data.w[1:]+ipm_data.w[:-1])/2, color='gray', alpha=.5, **kwargs)
        profile[profile<cut_lower*max(profile)] = 0
    ax.stairs(profile[1:-1], (ipm_data.w[1:]+ipm_data.w[:-1])/2, #color=cmap_petroff_10(0 if xy=='x' else 2),
              label=f'Profile ($t={at_time*SI.s:~L}$)', **kwargs)
    
    # Fit
    if not hasattr(fit, '__iter__'): fit = (fit,)
    ls = cycle(["--",":","-."])
    fitresults = []
    for fit in fit:
        p, pe, _, plot = fit(ipm_data.w, profile)
        if fit == fit_gaussian:
            s = SI.Measurement(p[3], pe[3], ipm_data.w_unit)
            label = f'Gaussian fit\n$\\sigma_{xy}={s:~L}$'
        elif fit == fit_lorenzian:
            s = SI.Measurement(p[3], pe[3], ipm_data.w_unit)
            label = f'Lorenzian fit\n$\\gamma_{xy}={s:~L}$'
        else:
            s = (p, pe)
            label = fit.__name__
        fitresults.append(s)
        ax.plot(*plot, ls=next(ls), label=label)
        ax.legend()
    
Philipp Niedermayer's avatar
Philipp Niedermayer committed
    ax.set(xlabel=f'{xy} / {ipm_data.w_unit}', ylabel=f'Intensity / {ipm_data.unit}', xlim=(min(ipm_data.w), max(ipm_data.w)), ylim=(0, ax.get_ylim()[1]))
    return fitresults[0] if len(fitresults)==1 else fitresults