Skip to content

Analysis Tasks Documentation

In this documentation there is a full explanation of all the tasks and their parameters that can be executed through the pipeline in the YAML configuration file.

Note: all the tasks have a context argument that contains all the analysis data and is constantly updated during the execution, but it must not be specified in the pipeline.

Calibration

Perform the calibration of the detector using pulse data at fixed voltages.

Parameters:

Name Type Description Default
context Context

The context object containing the pulse data in context.pulse as an instance of the class PulsatorFile.

required
charge_conversion bool

Whether to convert the calibration to charge (fC) or leave it in voltage (mV). Default is True.

charge_conversion
show bool

Whether to generate and show the plots of the calibration process. Default is True.

show

Returns:

Name Type Description
context Context

The updated context object containing the calibration results in context.conversion_model.

Source code in src/analysis/tasks.py
def calibration(
          context: Context,
          charge_conversion: bool = CalibrationDefaults.charge_conversion,
          show: bool = PlotDefaults.show
      ) -> Context:
    """Perform the calibration of the detector using pulse data at fixed voltages.

    Parameters
    ----------
    context : Context
        The context object containing the pulse data in `context.pulse` as an instance
        of the class PulsatorFile.
    charge_conversion : bool, optional
        Whether to convert the calibration to charge (fC) or leave it in voltage (mV).
        Default is True.
    show : bool, optional
        Whether to generate and show the plots of the calibration process. Default is True.

    Returns
    -------
    context : Context
        The updated context object containing the calibration results in
        `context.conversion_model`.
    """
    # Get the histogram of the data and plot it
    pulse = context.pulse
    hist = pulse.hist
    pulse_fig = plt.figure(pulse.file_path.name)
    hist.plot()
    # Find pulses and fit them with Gaussian models to find their positions
    xpeaks, _ = find_peaks_iterative(hist.bin_centers(), hist.content, pulse.num_pulses)
    mu_peak = np.zeros(pulse.num_pulses, dtype=object)
    for i, xpeak in enumerate(xpeaks):
        peak_model = aptapy.models.Gaussian()
        xmin = xpeak - np.sqrt(xpeak)
        xmax = xpeak + np.sqrt(xpeak)
        peak_model.fit_iterative(hist, xmin=xmin, xmax=xmax, absolute_sigma=True)
        mu_peak[i] = peak_model.mu.ufloat()
        peak_model.plot(fit_output=True)
    plt.legend()
    # Fit the data to find the calibration parameters
    ylabel = "Charge [fC]" if charge_conversion else "Voltage [mV]"
    model = aptapy.models.Line("Calibration", "ADC Channel", ylabel)
    xdata = unumpy.nominal_values(mu_peak)
    ydata = pulse.voltage
    model.fit(xdata, ydata)
    # Plot the calibration results
    cal_fig = plt.figure("Calibration")
    plt.errorbar(xdata, ydata, fmt=".k", label="Data")
    model.plot(fit_output=True, color=last_line_color())
    plt.legend()
    if not show:
        plt.close(pulse_fig)
        plt.close(cal_fig)
    # Update the context with the calibration model
    context.conversion_model = model
    # Update the context with the figures
    context.add_figure("pulse", pulse_fig)
    context.add_figure("calibration", cal_fig)
    return context

Spectral fitting

Perform the fitting of a spectral emission line in the source data.

Parameters:

Name Type Description Default
context Context

The context object containing the source data in context.last_source as an instance of the class SourceFile.

required
target str

The name of the fitting target.

required
model_class AbstractFitModel

The class of the model to use for fitting the spectral line. Default is Gaussian.

required
xmin float

The minimum x value to consider for the fit range. Default is -inf.

xmin
xmax float

The maximum x value to consider for the fit range. Default is inf.

xmax
num_sigma_left float

The number of sigmas to extend the fit range to the left of the peak. Default is 1.5.

num_sigma_left
num_sigma_right float

The number of sigmas to extend the fit range to the right of the peak. Default is 1.5.

num_sigma_right
absolute_sigma bool

Whether to use absolute sigma values for the fit. Default is True.

absolute_sigma

Returns:

Name Type Description
context Context

The updated context object containing the fit results.

Source code in src/analysis/tasks.py
def fit_peak(
          context: Context,
          target: str,
          model_class: list[type[AbstractFitModel]],
          xmin: float = FitPeakDefaults.xmin,
          xmax: float = FitPeakDefaults.xmax,
          num_sigma_left: float = FitPeakDefaults.num_sigma_left,
          num_sigma_right: float = FitPeakDefaults.num_sigma_right,
          absolute_sigma: bool = FitPeakDefaults.absolute_sigma,
          p0: list[float] | None = FitPeakDefaults.p0
      ) -> Context:
    """Perform the fitting of a spectral emission line in the source data.

    Parameters
    ----------
    context : Context
        The context object containing the source data in `context.last_source` as an instance
        of the class SourceFile.
    target: str
        The name of the fitting target.
    model_class : AbstractFitModel, optional
        The class of the model to use for fitting the spectral line. Default is Gaussian.
    xmin : float, optional
        The minimum x value to consider for the fit range. Default is -inf.
    xmax : float, optional
        The maximum x value to consider for the fit range. Default is inf.
    num_sigma_left : float, optional
        The number of sigmas to extend the fit range to the left of the peak. Default is 1.5.
    num_sigma_right : float, optional
        The number of sigmas to extend the fit range to the right of the peak. Default is 1.5.
    absolute_sigma : bool, optional
        Whether to use absolute sigma values for the fit. Default is True.

    Returns
    -------
    context : Context
        The updated context object containing the fit results.
    """
    # Access the last source data added to the context and get the histogram
    source = context.last_source
    hist = source.hist
    # Without a proper initialization of xmin and xmax the fit doesn't converge
    x_peak = hist.bin_centers()[hist.content > 0][5:][np.argmax(hist.content[hist.content > 0][5:])]
    if xmin == float("-inf") and xmax == float("inf"):
        xmin = x_peak - 0.3 * np.sqrt(x_peak)
        xmax = x_peak + 0.3 * np.sqrt(x_peak)
    # Define the dictionary of keyword arguments for the fit
    kwargs = dict(
         xmin=xmin,
         xmax=xmax,
         num_sigma_left=num_sigma_left,
         num_sigma_right=num_sigma_right,
         absolute_sigma=absolute_sigma,
         p0=p0)
    # Initialize the model and fit the data.
    # model_class is given as a list of models, even if it contains only one model, but so far
    # we only support fitting a single model at a time in this context.
    model = model_class[0]()
    if isinstance(model, aptapy.models.Fe55Forest):
        model.intensity1.freeze(0.16)   # type: ignore[attr-defined]
    model.fit_iterative(hist, **kwargs) # type: ignore[attr-defined]
    # Extract the line value and sigma from the fit results
    if isinstance(model, aptapy.models.Gaussian):
        line_val = model.status.correlated_pars[1]
        sigma = model.status.correlated_pars[2]
    elif isinstance(model, aptapy.models.Fe55Forest):
        reference_energy: float = model.energies[0]   # type: ignore [attr-defined]
        line_val = reference_energy / model.status.correlated_pars[1]
        sigma = model.status.correlated_pars[2]
    else:
        raise TypeError(f"Model of type {type(model)} not supported in fit_peak task")
    # Update the context with the fit results
    target_ctx = TargetContext(target, line_val, sigma, source.voltage, model)
    target_ctx.energy = context.config.acquisition.e_peak
    # Add the fwhm to the target context
    fwhm = SIGMA_TO_FWHM * sigma
    target_ctx.fwhm_val = fwhm
    # Save the target context in the main context
    context.add_target_ctx(source, target_ctx)
    return context

Gain estimate

Calculate the gain of the detector vs the back voltage using the fit results obtained from the source data of multiple files.

Parameters:

Name Type Description Default
context Context

The context object containing the fit results.

required
target str

The name of the fitting subtask to use for gain calculation.

required
w float

The W-value of the gas inside the detector. Default is 26.0 eV (Ar).

w
energy float

The energy of the emission line used for gain calculation. Default is 5.895 keV (Fe-55 Kα).

energy
fit bool

Whether to fit the gain trend with an exponential model. Default is True.

fit
show bool

Whether to show the plots of the gain trend. Default is True.

show

Returns:

Name Type Description
context Context

The updated context object containing the gain results.

Source code in src/analysis/tasks.py
def gain_task(
        context: Context,
        target: str,
        w: float = GainDefaults.w,
        energy: float = GainDefaults.energy,
        fit: bool = GainDefaults.fit,
        show: bool = PlotDefaults.show,
        ) -> Context:
    """Calculate the gain of the detector vs the back voltage using the fit results obtained from
    the source data of multiple files.

    Parameters
    ----------
    context : Context
        The context object containing the fit results.
    target : str
        The name of the fitting subtask to use for gain calculation.
    w : float, optional
        The W-value of the gas inside the detector. Default is 26.0 eV (Ar).
    energy : float, optional
        The energy of the emission line used for gain calculation. Default is 5.895 keV (Fe-55 Kα).
    fit : bool, optional
        Whether to fit the gain trend with an exponential model. Default is True.
    show : bool, optional
        Whether to show the plots of the gain trend. Default is True.

    Returns
    -------
    context : Context
        The updated context object containing the gain results.
    """
    # pylint: disable=invalid-unary-operand-type
    task = "gain"
    # Get the file names from the fit context keys
    file_names = context.file_names
    # Create empty arrays to store gain values and voltages
    gain_vals = np.zeros(len(file_names), dtype=object)
    voltages = np.zeros(len(file_names))
    # Iterate over all files and calculate the gain
    for i, file_name in enumerate(file_names):
        target_ctx = context.target_ctx(file_name, target)
        line_val = target_ctx.line_val
        voltages[i] = target_ctx.voltage
        target_ctx.gain_val = gain(w, line_val, energy)
        gain_vals[i] = target_ctx.gain_val
    # Save the results in the context
    context.add_task_results(task, target, dict(voltages=voltages, gain_vals=gain_vals))
    # If only a single file is analyzed, return the context without plotting or fitting
    if len(file_names) == 1:
        return context
    if fit:
        model = aptapy.models.Exponential()
        model.fit(voltages, unumpy.nominal_values(gain_vals),
                  sigma=unumpy.std_devs(gain_vals), absolute_sigma=True)
        context.add_task_fit_model(task, target, model)
    # Define the plot keyword arguments for style and labels
    style = context.config.style.tasks.get(task, PlotStyleConfig()).model_dump()
    plot_kwargs = dict(
        xlabel="Voltage [V]",
        ylabel="Gain",
        fig_name=f"gain_{target}",
        model0_label=get_model_label(task, model) if fit else None,
        show=show,
        **style)
    # Create the figure for the gain
    fig = plot_task(voltages, gain_vals, model, **plot_kwargs)
    # Add the figure to the context
    context.add_figure(task, fig)
    return context

Gain time trend

Calculate the gain of the detector vs time using the fit results obtained from the source data.

Parameters:

Name Type Description Default
context Context

The context object containing the fit results.

required
target str

The name of the fitting subtask to use for gain calculation. If None, no calculation is performed. Default is None.

required
w float

The W-value of the gas inside the detector. Default is 26.0 eV (Ar).

w
energy float

The energy of the emission line used for gain calculation. Default is 5.895 keV (Fe-55 Kα).

energy
subtasks list[str]

The list of fitting subtasks to fit the gain trend. If None, no fitting is performed. Default is None.

None
show bool

Whether to display the plot. Default is True.

show

Returns:

Name Type Description
context Context

The updated context object containing the gain trend and fit results.

Source code in src/analysis/tasks.py
def gain_trend(
        context: Context,
        target: str,
        w: float = GainDefaults.w,
        energy: float = GainDefaults.energy,
        subtasks: list[dict[str, Any]] | None = None,
        show: bool = GainDefaults.show,
    ) -> Context:
    """Calculate the gain of the detector vs time using the fit results obtained from the source
    data.

    Parameters
    ---------
    context : Context
        The context object containing the fit results.
    target : str
        The name of the fitting subtask to use for gain calculation. If None, no calculation is
        performed. Default is None.
    w : float, optional
        The W-value of the gas inside the detector. Default is 26.0 eV (Ar).
    energy : float, optional
        The energy of the emission line used for gain calculation. Default is 5.895 keV (Fe-55 Kα).
    subtasks : list[str], optional
        The list of fitting subtasks to fit the gain trend. If None, no fitting is performed.
        Default is None.
    show : bool, optional
        Whether to display the plot. Default is True.

    Returns
    -------
    context : Context
        The updated context object containing the gain trend and fit results.
    """
    task = "gain_trend"
    # Get the different file names
    file_names = context.file_names
    # Create empty arrays to store gain values and start times
    gain_vals = np.zeros(len(file_names), dtype=object)
    start_times = np.zeros(len(file_names), dtype=object)
    real_times = np.zeros(len(file_names))
    # Iterate over all files and calculate the gain values
    for i, file_name in enumerate(file_names):
        # Access the source data to extract the times
        source = context.source(file_name)
        start_times[i] = source.start_time
        real_times[i] = source.real_time
        # Access the target context and extract line value and voltage
        target_ctx = context.target_ctx(file_name, target)
        line_val = target_ctx.line_val
        target_ctx.gain_val = gain(w, line_val, energy)
        gain_vals[i] = target_ctx.gain_val
    # Calculate the accumulated time in hours
    times = amptek_accumulate_time(start_times, real_times) / 3600
    # Save the results in the context
    context.add_task_results(task, target, dict(times=times, gain_vals=gain_vals))
    # If fitting subtasks are provided, fit the gain trend with the specified models
    models = []
    model_labels = dict()
    if subtasks:
        for i, subtask in enumerate(subtasks):
            # Think how to refactor this part
            model_list = load_class(subtask["model"])
            model = model_list[0]()
            for m in model_list[1:]:
                model += m()
            fit_pars = subtask.get("fit_pars", {})
            kwargs = dict(
                xmin=fit_pars["xmin"],
                xmax=fit_pars["xmax"],
                absolute_sigma=fit_pars["absolute_sigma"],
                p0=fit_pars["p0"]
            )
            model.fit(times, unumpy.nominal_values(gain_vals),
                      sigma=unumpy.std_devs(gain_vals),
                      **kwargs)
            # model.plot(fit_output=True, plot_components=False)
            models.append(model)
            model_labels[f"model{i}_label"] = get_model_label(task, model)
            # Update the context with the fit results
            context.add_subtask_fit_model(task, target, subtask["target"], model)
            # context["results"][task][target][name] = dict(model=model)
    # Define the plot keyword arguments for style and labels
    style = context.config.style.tasks.get(task, PlotStyleConfig()).model_dump()
    plot_kwargs = dict(
        xlabel="Time from start [hours]",
        ylabel="Gain",
        fig_name=f"gain_trend_{target}",
        show=show,
        **model_labels,
        **style)
    # Create the figure for the gain trend
    fig = plot_task(times, gain_vals, *models, **plot_kwargs)
    # Add the figure to the context
    context.add_figure(task, fig)
    return context

Gain compare between folders

Compare the gain of multiple folders vs voltage using the fit results obtained from the source data.

Parameters:

Name Type Description Default
context FoldersContext

The context object containing the fit results.

required
target str

The name of the spectral line fitting subtask to use for gain comparison.

required
combine list[str]

List of folder names to combine for gain comparison. Default is an empty list.

combine
show bool

Whether to show the plots of the gain comparison. Default is True.

show

Returns:

Name Type Description
context FoldersContext

The updated context object containing the gain comparison results.

Source code in src/analysis/tasks.py
def compare_gain(
        context: FoldersContext,
        target: str,
        combine: list[str] = GainCompareDefaults.combine,
        show: bool = GainCompareDefaults.show,
        ) -> FoldersContext:
    """Compare the gain of multiple folders vs voltage using the fit results obtained from the
    source data.

    Parameters
    ---------
    context : FoldersContext
        The context object containing the fit results.
    target : str
        The name of the spectral line fitting subtask to use for gain comparison.
    combine : list[str], optional
        List of folder names to combine for gain comparison. Default is an empty list.
    show : bool, optional
        Whether to show the plots of the gain comparison. Default is True.

    Returns
    -------
    context : FoldersContext
        The updated context object containing the gain comparison results.
    """
    # pylint: disable=invalid-unary-operand-type
    task = "compare_gain"
    folders_style = context.config.style.folders
    # Get the different folder names
    folder_names = context.folder_names
    # Create empty arrays to store gain values and voltages
    y = np.zeros(len(combine), dtype=object)
    yerr = np.zeros(len(combine), dtype=object)
    x = np.zeros(len(combine), dtype=object)
    # Create the figure for the gain comparison
    fig, ax = plt.subplots(num="gain_comparison")
    # Iterate over all folders and plot the gain values
    j = 0
    for folder_name in folder_names:
        folder_ctx = context.folder_ctx(folder_name)
        folder_gain = folder_ctx.task_results("gain", target)
        gain_vals = folder_gain.get("gain_vals", [])
        voltages = folder_gain.get("voltages", [])
        # If not aggregating, plot each folder separately
        if folder_name not in combine:
            style = folders_style.get(folder_name, PlotStyleConfig()).model_dump()
            model = folder_gain.get("model", None)
            plot_kwargs = dict(
                model_label=get_model_label(task, model) if model else None,
                **style)
            plot_compare_task(ax, voltages, gain_vals, model, **plot_kwargs)
        # If aggregating, store the data together for later fitting and plotting
        else:
            y[j] = unumpy.nominal_values(gain_vals)
            yerr[j] = unumpy.std_devs(gain_vals)
            x[j] = voltages
            j += 1
    if combine:
        # Concatenate all data and fit with an exponential model
        y = np.concatenate(y)
        yerr = np.concatenate(yerr)
        x = np.concatenate(x)
        # Calculate the mean gain for each repeated voltage
        x, y, yerr = average_repeats(x, y, yerr)
        model = aptapy.models.Exponential()
        model.fit(x, y, sigma=yerr, absolute_sigma=True)
        # Plot the aggregated data and fit model
        style = folders_style.get("combine", PlotStyleConfig()).model_dump()
        plot_kwargs = dict(
            model_label=get_model_label(task, model),
            **style)
        plot_compare_task(ax, x, unumpy.uarray(y, yerr), model, **plot_kwargs)
        # Update the context with the fit results
        context.add_task_results(task, target, dict(model=model))
    # Define the task plot keyword arguments for style and labels
    task_style = context.config.style.tasks.get(task, PlotStyleConfig()).model_dump()
    # Set the title of the plot
    if task_style["title"] is not None:
        plt.title(task_style["title"])
    # Set the labels and the axis scales
    plt.xlabel("Voltage [V]")
    plt.ylabel("Gain")
    plt.xscale(task_style["xscale"])
    plt.yscale(task_style["yscale"])
    # Write the legend and show the plot
    write_legend(task_style["legend_label"], loc=task_style["legend_loc"])
    plt.tight_layout()
    if not show:
        plt.close(fig)
    # Add the figure to the context
    context.add_figure(task, fig)
    return context

Drift varying

Calculate the gain and rate of the detector vs the drift voltage using the fit results obtained from the source data of multiple files.

Parameters:

Name Type Description Default
context Context

The context object containing the fit results.

required
target str

The name of the fitting subtask to use for gain calculation. If None, no calculation is performed. Default is None.

required
w float

The W-value of the gas inside the detector. Default is 26.0 eV (Ar).

w
energy float

The energy of the emission line used for gain calculation. Default is 5.895 keV (Fe-55 Kα).

energy
threshold float

The energy threshold (in keV) above which to calculate the rate. Default is 1.5 keV.

threshold
show bool

Whether to show the plots of the gain vs drift voltage. Default is True.

show
rate bool

Whether to plot the rate on a secondary y-axis. Default is False.

rate
label str

The label for the gain trend plot. Default is None.

label
yscale str

The y-axis scale for the gain trend plot. Can be "linear" or "log". Default is "linear".

yscale

Returns:

Name Type Description
context Context

The updated context object containing the drift results.

Source code in src/analysis/tasks.py
def drift(
        context: Context,
        target: str,
        w: float = GainDefaults.w,
        energy: float = GainDefaults.energy,
        threshold: float = DriftDefaults.threshold,
        show: bool = DriftDefaults.show,
        rate: bool = DriftDefaults.rate,
        label: str | None = DriftDefaults.label,
        yscale: str = DriftDefaults.yscale,
        ) -> Context:
    """Calculate the gain and rate of the detector vs the drift voltage using the fit results
    obtained from the source data of multiple files.

    Parameters
    ---------
    context : Context
        The context object containing the fit results.
    target : str
        The name of the fitting subtask to use for gain calculation. If None, no calculation is
        performed. Default is None.
    w : float, optional
        The W-value of the gas inside the detector. Default is 26.0 eV (Ar).
    energy : float, optional
        The energy of the emission line used for gain calculation. Default is 5.895 keV (Fe-55 Kα).
    threshold : float, optional
        The energy threshold (in keV) above which to calculate the rate. Default is 1.5 keV.
    show : bool, optional
        Whether to show the plots of the gain vs drift voltage. Default is True.
    rate : bool, optional
        Whether to plot the rate on a secondary y-axis. Default is False.
    label : str, optional
        The label for the gain trend plot. Default is None.
    yscale : str, optional
        The y-axis scale for the gain trend plot. Can be "linear" or "log". Default is "linear".

    Returns
    -------
    context : Context
        The updated context object containing the drift results.
    """
    task = "drift"
    # Get the different file names and create arrays to store rate values and drift voltages
    file_names = context.file_names
    rates = np.zeros(len(file_names), dtype=object)
    drift_voltages = np.zeros(len(file_names))
    gain_vals = np.zeros(len(file_names), dtype=object)
    # Iterate over all files and calculate the rate values
    for i, file_name in enumerate(file_names):
        source = context.source(file_name)
        drift_voltages[i] = source.drift_voltage
        integration_time = source.real_time
        target_ctx = context.target_ctx(file_name, target)
        line_val = target_ctx.line_val
        gain_vals[i] = gain(w, line_val, energy)
        # Calculate the threshold in charge and calculate the rate
        charge_thr = (threshold / energy) * line_val
        hist = source.hist
        counts = hist.content[hist.bin_centers() > charge_thr.n].sum()
        rates[i] = counts / integration_time
    context.add_task_results(task, target, dict(drift_voltages=drift_voltages,
                                               gain_vals=gain_vals,
                                               rates=rates))
    # Prepare the quantities for plotting
    y = unumpy.nominal_values(gain_vals)
    yerr = unumpy.std_devs(gain_vals)
    y_rate = unumpy.nominal_values(rates)
    yerr_rate = unumpy.std_devs(rates)
    # Plot the gain vs drift voltage
    fig, ax1 = plt.subplots(num="drift_voltage")
    ax1.errorbar(drift_voltages, y, yerr=yerr, fmt=".k", label="Gain")
    ax1.set_xlabel("Drift Voltage [V]")
    ax1.set_ylabel("Gain", color=last_line_color())
    ax1.tick_params(axis="y", labelcolor=last_line_color())
    ax1.set_yscale(yscale)
    # Plot the rate on a secondary y-axis if requested
    if rate:
        color = "red"
        ax2 = ax1.twinx()
        ax2.errorbar(drift_voltages, y_rate, yerr=yerr_rate, fmt=".", color=color, label="Rate")
        ax2.set_ylabel("Rate [counts/s]", color=color)
        ax2.tick_params(axis="y",labelcolor=color)
    axs = (ax1, ax2) if rate else (ax1, )
    write_legend(label, *axs, loc="lower right")
    if not show:
        plt.close(fig)
    # Add the figure to the context
    context.add_figure(task, fig)
    return context

Resolution estimate

Calculate the energy resolution of the detector using the fit results obtained from the source data. This estimate is based on the position and the width of the target spectral line.

Parameters:

Name Type Description Default
context Context

The context object containing the fit results.

required
target str

The name of the fitting subtask to use for resolution calculation. If None, no calculation is performed. Default is None.

required
show bool

Whether to show the plots of the resolution trend. Default is True.

show

Returns:

Name Type Description
context Context

The updated context object containing the resolution results.

Source code in src/analysis/tasks.py
def resolution_task(
        context: Context,
        target: str,
        show: bool = ResolutionDefaults.show,
        ) -> Context:
    """Calculate the energy resolution of the detector using the fit results obtained from the
    source data. This estimate is based on the position and the width of the target spectral
    line.

    Parameters
    ---------
    context : Context
        The context object containing the fit results.
    target : str, optional
        The name of the fitting subtask to use for resolution calculation. If None, no calculation
        is performed. Default is None.
    show : bool, optional
        Whether to show the plots of the resolution trend. Default is True.

    Returns
    -------
    context : Context
        The updated context object containing the resolution results.
    """
    task = "resolution"
    # Get the file names from the fit context keys
    file_names = context.file_names
    # Create empty arrays to store resolution values and voltages
    res_vals = np.zeros(len(file_names), dtype=object)
    voltages = np.zeros(len(file_names))
    # Iterate over all files and calculate the gain
    for i, file_name in enumerate(file_names):
        # Access the target context and extract line value, sigma and voltage
        target_ctx = context.target_ctx(file_name, target)
        line_val = target_ctx.line_val
        sigma = target_ctx.sigma
        voltages[i] = target_ctx.voltage
        target_ctx.res_val = energy_resolution(line_val, sigma)
        res_vals[i] = target_ctx.res_val
    # Save the results in the context
    context.add_task_results(task, target, dict(voltages=voltages, res_vals=res_vals))
    # If only a single file is analyzed, return the context without plotting
    if len(file_names) == 1:
        return context
    # Define the plot keyword arguments for style and labels
    style = context.config.style.tasks.get(task, PlotStyleConfig()).model_dump()
    plot_kwargs = dict(
        xlabel="Voltage [V]",
        ylabel=r"$\Delta$E/E",
        fig_name=f"resolution_{target}",
        show=show,
        **style)
    # Create the figure for the resolution trend
    fig = plot_task(voltages, res_vals, **plot_kwargs)
    # Add the figure to the context
    context.add_figure(task, fig)
    return context

Resolution estimate with escape peak

Calculate the energy resolution of the detector using the fit results obtained from the source data. This calculation is based on the position and width of the main spectral line and the position of the escape peak.

Parameters:

Name Type Description Default
context Context

The context object containing the fit results.

required
target_main str

The name of the fitting subtask corresponding to the main spectral line. If None, no calculation is performed. Default is None.

required
target_escape str

The name of the fitting subtask corresponding to the escape peak. If None, no calculation is performed. Default is None.

required
show bool

Whether to show the plots of the resolution trend. Default is True.

show

Returns:

Name Type Description
context Context

The updated context object containing the resolution results.

Source code in src/analysis/tasks.py
def resolution_escape(
        context: Context,
        target_main: str,
        target_escape: str,
        show: bool = ResolutionDefaults.show,
        ) -> Context:
    """Calculate the energy resolution of the detector using the fit results obtained from the
    source data. This calculation is based on the position and width of the main spectral line and
    the position of the escape peak.

    Parameters
    ---------
    context : Context
        The context object containing the fit results.
    target_main : str, optional
        The name of the fitting subtask corresponding to the main spectral line. If None, no
        calculation is performed. Default is None.
    target_escape : str, optional
        The name of the fitting subtask corresponding to the escape peak. If None, no calculation is
        performed. Default is None.
    show : bool, optional
        Whether to show the plots of the resolution trend. Default is True.

    Returns
    -------
    context : Context
        The updated context object containing the resolution results.
    """
    task = "resolution_escape"
    # Get the single file names from the fit context keys
    file_names = context.file_names
    # Create empty arrays to store resolution values and voltages
    res_vals = np.zeros(len(file_names), dtype=object)
    voltages = np.zeros(len(file_names))
    for i, file_name in enumerate(file_names):
        # Access the target contexts and extract line values and sigma
        target_ctx = context.target_ctx(file_name, target_main)
        line_val_main = target_ctx.line_val
        sigma_main = target_ctx.sigma
        voltages[i] = target_ctx.voltage
        line_val_esc = context.target_ctx(file_name, target_escape).line_val
        # Calculate the energy resolution using the escape peak and update the context
        target_ctx.res_escape_val = energy_resolution_escape(line_val_main,
                                                             line_val_esc,
                                                             sigma_main)
        res_vals[i] = target_ctx.res_escape_val
    # Save the results in the context
    context.add_task_results(task, target_main, dict(voltages=voltages, res_vals=res_vals))
    # If only a single file is analyzed, return the context without plotting
    if len(file_names) == 1:
        return context
    # Create the figure for the resolution trend
    style = context.config.style.tasks.get(task, PlotStyleConfig()).model_dump()
    plot_kwargs = dict(
        xlabel="Voltage [V]",
        ylabel=r"$\Delta$E/E (escape)",
        fig_name=f"resolution_esc_{target_main}",
        show=show,
        **style)
    fig = plot_task(voltages, res_vals, **plot_kwargs)
    context.add_figure(task, fig)
    return context

Resolution compare between folders

Plot the spectrum

Plot the spectra from the source data and overlay the fitted models for the specified targets.

Parameters:

Name Type Description Default
context Context

The context object containing the source data the fit results.

required
targets list[str]

The list of fitting subtask names to plot the fitted models for. If None, no models are plotted. Default is None.

None
title str

The title for the plot. Default is None.

title
label str

The label for the plot legend. Default is None.

label
task_labels list[str]

The list of task names to use for generating the labels of the fitted models. If None, no labels are generated. Default is None.

task_labels
loc str

The location of the legend in the plot. Default is "best".

loc
xrange list[float]

The x-axis range for the plot. If None, the range is automatically calculated based on the data and fitted models. Default is None.

xrange
xmin_factor float

The factor to apply to the minimum x value when automatically calculating the xrange. Default is 1.0.

xmin_factor
xmax_factor float

The factor to apply to the maximum x value when automatically calculating the xrange. Default is 1.0.

xmax_factor
voltage bool

Whether to include the voltage information in the legend. Default is False.

voltage
show bool

Whether to show the plots after creation. Default is True.

show

Returns:

Name Type Description
context Context

The context object (in future it will be updated with the figures).

Source code in src/analysis/tasks.py
def plot_spectrum(
        context: Context,
        targets: list[str] | None = None,
        title: str | None = PlotDefaults.title,
        label: str | None = PlotDefaults.label,
        task_labels: list[str] | None = PlotDefaults.task_labels,
        loc: str = PlotDefaults.loc,
        xrange: list[float] | None = PlotDefaults.xrange,
        xmin_factor: float = PlotDefaults.xmin_factor,
        xmax_factor: float = PlotDefaults.xmax_factor,
        voltage: bool = PlotDefaults.voltage,
        show: bool = PlotDefaults.show
        ) -> Context:
    """Plot the spectra from the source data and overlay the fitted models for the specified
    targets.

    Parameters
    ---------
    context : Context
        The context object containing the source data the fit results.
    targets : list[str], optional
        The list of fitting subtask names to plot the fitted models for. If None, no models are
        plotted. Default is None.
    title : str, optional
        The title for the plot. Default is None.
    label : str, optional
        The label for the plot legend. Default is None.
    task_labels : list[str], optional
        The list of task names to use for generating the labels of the fitted models. If None,
        no labels are generated. Default is None.
    loc : str, optional
        The location of the legend in the plot. Default is "best".
    xrange : list[float], optional
        The x-axis range for the plot. If None, the range is automatically calculated based on
        the data and fitted models. Default is None.
    xmin_factor : float, optional
        The factor to apply to the minimum x value when automatically calculating the xrange.
        Default is 1.0.
    xmax_factor : float, optional
        The factor to apply to the maximum x value when automatically calculating the xrange.
        Default is 1.0.
    voltage: bool, optional
        Whether to include the voltage information in the legend. Default is False.
    show : bool, optional
        Whether to show the plots after creation. Default is True.

    Returns
    -------
    context : Context
        The context object (in future it will be updated with the figures).
    """
    # Get the file names from the sources keys
    file_names = context.file_names
    # Iterate over all files and plot the spectra with fitted models, if desired
    for file_name in file_names:
        source = context.source(file_name)
        # Create the plot figure and plot the spectrum and set the title
        fig = plt.figure(f"{source.file_path.stem}_{targets}")
        if title is not None:
            plt.title(title)
        source.hist.plot(label="Data")
        # Plot the fitted models for the specified targets and get labels
        models = []
        if targets is not None:
            for target in targets:
                target_ctx = context.target_ctx(file_name, target)
                model = target_ctx.model
                model_label = get_label(task_labels, target_ctx)
                # Save the model for automatic xrange calculation
                models.append(model)
                model.plot(label=model_label)
        # Set the x-axis range
        plt.xlim(xrange)
        if xrange is None:
            _xrange = get_xrange(source, models)
            plt.xlim(_xrange[0] * xmin_factor, _xrange[1] * xmax_factor)
        _label = label
        # If voltage info is requested, add it to the legend
        if voltage and _label is not None:
            _label += f"\nBack: {source.voltage:>4.0f} V\nDrift: {source.drift_voltage:>4.0f} V"
        write_legend(_label, loc=loc)
        # Add the figure to the context
        context.add_figure(file_name, fig)
        plt.tight_layout()
        if not show:
            plt.close(fig)
    return context