Module pylars.processing.pulses

Expand source code
from typing import List

import numba as nb
import numpy as np


class pulse_processing():
    """All the things pulses.
    """

    @classmethod
    def get_area(cls, waveform: np.ndarray, baseline_value: float,
                 pulse_start: int, pulse_end: int, dt: int = 10,
                 negative_polarity: bool = True,
                 baseline_subtracted: bool = False) -> float:
        """Get area of a single identified pulse in a waveform. Points to
        the numbafied function `_get_area`.
        """
        area_under = _get_area(
            waveform,
            baseline_value,
            pulse_start,
            pulse_end,
            dt,
            negative_polarity,
            baseline_subtracted)
        return area_under

    @classmethod
    def get_all_areas(cls, waveform: np.ndarray, pulses: list,
                      baseline_value: float, dt: int = 10,
                      negative_polarity: bool = True,
                      baseline_subtracted: bool = False) -> np.ndarray:
        """Compute the areas of all the pulses in a waveform.
        TO DO: use np.apply_along_axis or similar and see if
        there is speed improvement.
        """
        areas = np.zeros(len(pulses))
        for i, _pulse in enumerate(pulses):
            areas[i] = cls.get_area(
                waveform, baseline_value, _pulse[0], _pulse[-1],
                dt,
                negative_polarity,
                baseline_subtracted)
        return areas

    @classmethod
    def get_all_lengths(cls, pulses: list) -> list:
        """Compute the lengths of all the pulses in a waveform.
        (It's faster without @numba.njit)

        Args:
            pulses (list): list of arrays where the elements are the index
                of samples within each pulse.
        Returns:
            list: list with the lenght for each pulse.
        """
        lengths = [len(_pulse) for _pulse in pulses]
        return lengths

    @classmethod
    def get_all_positions(cls, pulses: list) -> list:
        """Calcultes the initial position of the identified pulse
        in number of samples.

        (Faster without numba...?)

        Args:
            pulses (list): array of identified pulses.

        Returns:
            list: list of positions of pulses.
        """
        positions = [_pulse[0] for _pulse in pulses]
        return positions

    @classmethod
    def get_amplitude(cls, waveform: np.ndarray, baseline_value: float,
                      peak_start: int, peak_end: int,
                      negative_polarity: bool = True,
                      baseline_subtracted: bool = False) -> float:
        """Get area of a single identified pulse in a waveform. Points to
        the numbafied function `_get_amplitude`.
        """
        amplitude = _get_amplitude(
            waveform,
            baseline_value,
            peak_start,
            peak_end,
            negative_polarity,
            baseline_subtracted)
        return amplitude

    @classmethod
    def get_all_amplitudes(cls, waveform: np.ndarray, pulses: list,
                           baseline_value: float,
                           negative_polarity: bool = True,
                           baseline_subtracted: bool = False) -> List[float]:
        """Calcultes the max amplitude of the identified pulse
        in number of samples.

        (Faster without numba...?)

        Args:
            waveform (np.ndarray): 1D array of all the ADC counts where each
                element is a sample number in the waveform. The length of the
                array is the ammount of samples in the waveform.
            pulses (np.ndarray): array of identified pulse.
            baseline_value (float): value of ADC counts to use as baseline.

        Returns:
            list: list of amplitudes of pulses.
        """
        amplitudes = np.zeros(len(pulses))
        for i, _peak in enumerate(pulses):
            amplitudes[i] = cls.get_amplitude(
                waveform, baseline_value, _peak[0], _peak[-1],
                negative_polarity,
                baseline_subtracted)
        return amplitudes.tolist()

    @classmethod
    def split_consecutive(cls, array: np.ndarray, stepsize: int = 1) -> list:
        """Splits an array into several where values are consecutive
        to each other. Points to numbafied function _split_consecutive().
        """
        split_array = _split_consecutive(array, stepsize)
        return split_array

    @classmethod
    def find_pulses_simple(cls, waveform_array: np.ndarray,
                           baseline_value: float, std_value: float,
                           sigma_lvl: float = 5, negative_polarity: bool = True,
                           baseline_subtracted: bool = False) -> list:
        """Pulse processing to find pulses above sigma times baseline rms.

        Args:
           waveform_array (np.ndarray): 1D array of all the ADC counts where
                each element is a sample number in the waveform. The length of the
                array is the ammount of samples in the waveform.
            baseline_value (float): value of ADC counts to use as baseline.
            std_value (float): standard deviation of the calculated baseline
                value.
            sigma_lvl (float, optional): number of times above baseline in
                stds to consider as new pulse. Defaults to 5.
            negative_polarity (bool): Polarity of the signal, True for
                negative (as standard SiPM waveforms), False for positive.
                Defaults to True.
            baseline_subtracted (bool): info if the baseline is already
                subtracted in the waveform. Defaults to False.

        Returns:
            list: list with the found pulses.
        """

        if baseline_subtracted:
            baseline_value = 0

        if negative_polarity:
            above_baseline = np.where(
                waveform_array < (
                    baseline_value -
                    std_value *
                    sigma_lvl))[0]

        elif not negative_polarity:
            above_baseline = np.where(
                waveform_array > (
                    baseline_value +
                    std_value *
                    sigma_lvl))[0]

        pulses = cls.split_consecutive(above_baseline)  # type: ignore

        return pulses


@nb.njit
def _get_area(waveform: np.ndarray, baseline_value: float,
              pulse_start: int, pulse_end: int, dt: int = 10,
              negative_polarity: bool = True,
              baseline_subtracted: bool = False) -> float:
    """Get area of a single identified pulse in a waveform. Numbafied.

    Args:
        waveform (np.ndarray): 1D array of all the ADC counts where each
            element is a sample number in the waveform. The length of the array is the
            ammount of samples in the waveform.
        baseline_value (float): value of ADC counts to use as baseline.
        peak_start (int): index of start of the pulse
        peak_end (int): index of end of the pulse
        dt (int, optional): duration of each sample in the waveform in
            nanoseconds. Defaults to 10.
        negative_polarity (bool): Polarity of the signal, True for
                negative (as standard SiPM waveforms), False for positive.
                Defaults to True.
            baseline_subtracted (bool): info if the baseline is already
                subtracted in the waveform. Defaults to False.

    Returns:
        float: return calculated integrated ADC counts.
    """

    if baseline_subtracted:
        baseline_value = 0

    if negative_polarity:
        polarity = -1
    else:
        polarity = 1

    pulse_wf = waveform[pulse_start:pulse_end]
    area_under = polarity * dt * np.sum(pulse_wf - baseline_value)
    return area_under


@nb.njit
def _get_amplitude(waveform: np.ndarray, baseline_value: float,
                   peak_start: int, peak_end: int,
                   negative_polarity: bool = True,
                   baseline_subtracted: bool = False) -> float:
    """Get area of a single identified pulse in a waveform. Numbafied.

    Args:
        waveform (np.ndarray): 1D array of all the ADC counts where each
            element is a sample number in the waveform. The length of the array is the
            ammount of samples in the waveform.
        baseline_value (float): value of ADC counts to use as baseline.
        peak_start (int): index of start of the pulse
        peak_end (int): index of end of the pulse
        negative_polarity (bool): Polarity of the signal, True for
                negative (as standard SiPM waveforms), False for positive.
                Defaults to True.
            baseline_subtracted (bool): info if the baseline is already
                subtracted in the waveform. Defaults to False.

    Returns:
        float: return amplitude of pulse in ADC counts (minimum value of
            ADC counts registered in pulse)
    """
    peak_wf = waveform[peak_start:peak_end]

    if baseline_subtracted:
        baseline_value = 0

    if len(peak_wf) > 0:
        if negative_polarity:
            amplitude = min(peak_wf)
        else:
            amplitude = max(peak_wf)
    else:
        amplitude = baseline_value
    return amplitude


@nb.njit
def _split_consecutive(array: np.ndarray, stepsize: int = 1) -> list:
    """Splits an array into several where values are consecutive
    to each other, in a numbafied verison.

    Args:
        array (np.ndarray): array of indexes recognised as pulses.
        stepsize (int, optional): minimum consecutive indexes to split into
            different pulses. Defaults to 1.

    Returns:
        np.ndarray: array with splitted pulses.
    """
    split_index = np.where(np.diff(array) != stepsize)[0] + 1
    split_array = np.split(array, split_index)

    return split_array

Classes

class pulse_processing

All the things pulses.

Expand source code
class pulse_processing():
    """All the things pulses.
    """

    @classmethod
    def get_area(cls, waveform: np.ndarray, baseline_value: float,
                 pulse_start: int, pulse_end: int, dt: int = 10,
                 negative_polarity: bool = True,
                 baseline_subtracted: bool = False) -> float:
        """Get area of a single identified pulse in a waveform. Points to
        the numbafied function `_get_area`.
        """
        area_under = _get_area(
            waveform,
            baseline_value,
            pulse_start,
            pulse_end,
            dt,
            negative_polarity,
            baseline_subtracted)
        return area_under

    @classmethod
    def get_all_areas(cls, waveform: np.ndarray, pulses: list,
                      baseline_value: float, dt: int = 10,
                      negative_polarity: bool = True,
                      baseline_subtracted: bool = False) -> np.ndarray:
        """Compute the areas of all the pulses in a waveform.
        TO DO: use np.apply_along_axis or similar and see if
        there is speed improvement.
        """
        areas = np.zeros(len(pulses))
        for i, _pulse in enumerate(pulses):
            areas[i] = cls.get_area(
                waveform, baseline_value, _pulse[0], _pulse[-1],
                dt,
                negative_polarity,
                baseline_subtracted)
        return areas

    @classmethod
    def get_all_lengths(cls, pulses: list) -> list:
        """Compute the lengths of all the pulses in a waveform.
        (It's faster without @numba.njit)

        Args:
            pulses (list): list of arrays where the elements are the index
                of samples within each pulse.
        Returns:
            list: list with the lenght for each pulse.
        """
        lengths = [len(_pulse) for _pulse in pulses]
        return lengths

    @classmethod
    def get_all_positions(cls, pulses: list) -> list:
        """Calcultes the initial position of the identified pulse
        in number of samples.

        (Faster without numba...?)

        Args:
            pulses (list): array of identified pulses.

        Returns:
            list: list of positions of pulses.
        """
        positions = [_pulse[0] for _pulse in pulses]
        return positions

    @classmethod
    def get_amplitude(cls, waveform: np.ndarray, baseline_value: float,
                      peak_start: int, peak_end: int,
                      negative_polarity: bool = True,
                      baseline_subtracted: bool = False) -> float:
        """Get area of a single identified pulse in a waveform. Points to
        the numbafied function `_get_amplitude`.
        """
        amplitude = _get_amplitude(
            waveform,
            baseline_value,
            peak_start,
            peak_end,
            negative_polarity,
            baseline_subtracted)
        return amplitude

    @classmethod
    def get_all_amplitudes(cls, waveform: np.ndarray, pulses: list,
                           baseline_value: float,
                           negative_polarity: bool = True,
                           baseline_subtracted: bool = False) -> List[float]:
        """Calcultes the max amplitude of the identified pulse
        in number of samples.

        (Faster without numba...?)

        Args:
            waveform (np.ndarray): 1D array of all the ADC counts where each
                element is a sample number in the waveform. The length of the
                array is the ammount of samples in the waveform.
            pulses (np.ndarray): array of identified pulse.
            baseline_value (float): value of ADC counts to use as baseline.

        Returns:
            list: list of amplitudes of pulses.
        """
        amplitudes = np.zeros(len(pulses))
        for i, _peak in enumerate(pulses):
            amplitudes[i] = cls.get_amplitude(
                waveform, baseline_value, _peak[0], _peak[-1],
                negative_polarity,
                baseline_subtracted)
        return amplitudes.tolist()

    @classmethod
    def split_consecutive(cls, array: np.ndarray, stepsize: int = 1) -> list:
        """Splits an array into several where values are consecutive
        to each other. Points to numbafied function _split_consecutive().
        """
        split_array = _split_consecutive(array, stepsize)
        return split_array

    @classmethod
    def find_pulses_simple(cls, waveform_array: np.ndarray,
                           baseline_value: float, std_value: float,
                           sigma_lvl: float = 5, negative_polarity: bool = True,
                           baseline_subtracted: bool = False) -> list:
        """Pulse processing to find pulses above sigma times baseline rms.

        Args:
           waveform_array (np.ndarray): 1D array of all the ADC counts where
                each element is a sample number in the waveform. The length of the
                array is the ammount of samples in the waveform.
            baseline_value (float): value of ADC counts to use as baseline.
            std_value (float): standard deviation of the calculated baseline
                value.
            sigma_lvl (float, optional): number of times above baseline in
                stds to consider as new pulse. Defaults to 5.
            negative_polarity (bool): Polarity of the signal, True for
                negative (as standard SiPM waveforms), False for positive.
                Defaults to True.
            baseline_subtracted (bool): info if the baseline is already
                subtracted in the waveform. Defaults to False.

        Returns:
            list: list with the found pulses.
        """

        if baseline_subtracted:
            baseline_value = 0

        if negative_polarity:
            above_baseline = np.where(
                waveform_array < (
                    baseline_value -
                    std_value *
                    sigma_lvl))[0]

        elif not negative_polarity:
            above_baseline = np.where(
                waveform_array > (
                    baseline_value +
                    std_value *
                    sigma_lvl))[0]

        pulses = cls.split_consecutive(above_baseline)  # type: ignore

        return pulses

Static methods

def find_pulses_simple(waveform_array: numpy.ndarray, baseline_value: float, std_value: float, sigma_lvl: float = 5, negative_polarity: bool = True, baseline_subtracted: bool = False) ‑> list

Pulse processing to find pulses above sigma times baseline rms.

Args

waveform_array : np.ndarray
1D array of all the ADC counts where each element is a sample number in the waveform. The length of the array is the ammount of samples in the waveform.

baseline_value (float): value of ADC counts to use as baseline. std_value (float): standard deviation of the calculated baseline value. sigma_lvl (float, optional): number of times above baseline in stds to consider as new pulse. Defaults to 5. negative_polarity (bool): Polarity of the signal, True for negative (as standard SiPM waveforms), False for positive. Defaults to True. baseline_subtracted (bool): info if the baseline is already subtracted in the waveform. Defaults to False.

Returns

list
list with the found pulses.
Expand source code
@classmethod
def find_pulses_simple(cls, waveform_array: np.ndarray,
                       baseline_value: float, std_value: float,
                       sigma_lvl: float = 5, negative_polarity: bool = True,
                       baseline_subtracted: bool = False) -> list:
    """Pulse processing to find pulses above sigma times baseline rms.

    Args:
       waveform_array (np.ndarray): 1D array of all the ADC counts where
            each element is a sample number in the waveform. The length of the
            array is the ammount of samples in the waveform.
        baseline_value (float): value of ADC counts to use as baseline.
        std_value (float): standard deviation of the calculated baseline
            value.
        sigma_lvl (float, optional): number of times above baseline in
            stds to consider as new pulse. Defaults to 5.
        negative_polarity (bool): Polarity of the signal, True for
            negative (as standard SiPM waveforms), False for positive.
            Defaults to True.
        baseline_subtracted (bool): info if the baseline is already
            subtracted in the waveform. Defaults to False.

    Returns:
        list: list with the found pulses.
    """

    if baseline_subtracted:
        baseline_value = 0

    if negative_polarity:
        above_baseline = np.where(
            waveform_array < (
                baseline_value -
                std_value *
                sigma_lvl))[0]

    elif not negative_polarity:
        above_baseline = np.where(
            waveform_array > (
                baseline_value +
                std_value *
                sigma_lvl))[0]

    pulses = cls.split_consecutive(above_baseline)  # type: ignore

    return pulses
def get_all_amplitudes(waveform: numpy.ndarray, pulses: list, baseline_value: float, negative_polarity: bool = True, baseline_subtracted: bool = False) ‑> List[float]

Calcultes the max amplitude of the identified pulse in number of samples.

(Faster without numba…?)

Args

waveform : np.ndarray
1D array of all the ADC counts where each element is a sample number in the waveform. The length of the array is the ammount of samples in the waveform.
pulses : np.ndarray
array of identified pulse.
baseline_value : float
value of ADC counts to use as baseline.

Returns

list
list of amplitudes of pulses.
Expand source code
@classmethod
def get_all_amplitudes(cls, waveform: np.ndarray, pulses: list,
                       baseline_value: float,
                       negative_polarity: bool = True,
                       baseline_subtracted: bool = False) -> List[float]:
    """Calcultes the max amplitude of the identified pulse
    in number of samples.

    (Faster without numba...?)

    Args:
        waveform (np.ndarray): 1D array of all the ADC counts where each
            element is a sample number in the waveform. The length of the
            array is the ammount of samples in the waveform.
        pulses (np.ndarray): array of identified pulse.
        baseline_value (float): value of ADC counts to use as baseline.

    Returns:
        list: list of amplitudes of pulses.
    """
    amplitudes = np.zeros(len(pulses))
    for i, _peak in enumerate(pulses):
        amplitudes[i] = cls.get_amplitude(
            waveform, baseline_value, _peak[0], _peak[-1],
            negative_polarity,
            baseline_subtracted)
    return amplitudes.tolist()
def get_all_areas(waveform: numpy.ndarray, pulses: list, baseline_value: float, dt: int = 10, negative_polarity: bool = True, baseline_subtracted: bool = False) ‑> numpy.ndarray

Compute the areas of all the pulses in a waveform. TO DO: use np.apply_along_axis or similar and see if there is speed improvement.

Expand source code
@classmethod
def get_all_areas(cls, waveform: np.ndarray, pulses: list,
                  baseline_value: float, dt: int = 10,
                  negative_polarity: bool = True,
                  baseline_subtracted: bool = False) -> np.ndarray:
    """Compute the areas of all the pulses in a waveform.
    TO DO: use np.apply_along_axis or similar and see if
    there is speed improvement.
    """
    areas = np.zeros(len(pulses))
    for i, _pulse in enumerate(pulses):
        areas[i] = cls.get_area(
            waveform, baseline_value, _pulse[0], _pulse[-1],
            dt,
            negative_polarity,
            baseline_subtracted)
    return areas
def get_all_lengths(pulses: list) ‑> list

Compute the lengths of all the pulses in a waveform. (It's faster without @numba.njit)

Args

pulses : list
list of arrays where the elements are the index of samples within each pulse.

Returns

list
list with the lenght for each pulse.
Expand source code
@classmethod
def get_all_lengths(cls, pulses: list) -> list:
    """Compute the lengths of all the pulses in a waveform.
    (It's faster without @numba.njit)

    Args:
        pulses (list): list of arrays where the elements are the index
            of samples within each pulse.
    Returns:
        list: list with the lenght for each pulse.
    """
    lengths = [len(_pulse) for _pulse in pulses]
    return lengths
def get_all_positions(pulses: list) ‑> list

Calcultes the initial position of the identified pulse in number of samples.

(Faster without numba…?)

Args

pulses : list
array of identified pulses.

Returns

list
list of positions of pulses.
Expand source code
@classmethod
def get_all_positions(cls, pulses: list) -> list:
    """Calcultes the initial position of the identified pulse
    in number of samples.

    (Faster without numba...?)

    Args:
        pulses (list): array of identified pulses.

    Returns:
        list: list of positions of pulses.
    """
    positions = [_pulse[0] for _pulse in pulses]
    return positions
def get_amplitude(waveform: numpy.ndarray, baseline_value: float, peak_start: int, peak_end: int, negative_polarity: bool = True, baseline_subtracted: bool = False) ‑> float

Get area of a single identified pulse in a waveform. Points to the numbafied function _get_amplitude.

Expand source code
@classmethod
def get_amplitude(cls, waveform: np.ndarray, baseline_value: float,
                  peak_start: int, peak_end: int,
                  negative_polarity: bool = True,
                  baseline_subtracted: bool = False) -> float:
    """Get area of a single identified pulse in a waveform. Points to
    the numbafied function `_get_amplitude`.
    """
    amplitude = _get_amplitude(
        waveform,
        baseline_value,
        peak_start,
        peak_end,
        negative_polarity,
        baseline_subtracted)
    return amplitude
def get_area(waveform: numpy.ndarray, baseline_value: float, pulse_start: int, pulse_end: int, dt: int = 10, negative_polarity: bool = True, baseline_subtracted: bool = False) ‑> float

Get area of a single identified pulse in a waveform. Points to the numbafied function _get_area.

Expand source code
@classmethod
def get_area(cls, waveform: np.ndarray, baseline_value: float,
             pulse_start: int, pulse_end: int, dt: int = 10,
             negative_polarity: bool = True,
             baseline_subtracted: bool = False) -> float:
    """Get area of a single identified pulse in a waveform. Points to
    the numbafied function `_get_area`.
    """
    area_under = _get_area(
        waveform,
        baseline_value,
        pulse_start,
        pulse_end,
        dt,
        negative_polarity,
        baseline_subtracted)
    return area_under
def split_consecutive(array: numpy.ndarray, stepsize: int = 1) ‑> list

Splits an array into several where values are consecutive to each other. Points to numbafied function _split_consecutive().

Expand source code
@classmethod
def split_consecutive(cls, array: np.ndarray, stepsize: int = 1) -> list:
    """Splits an array into several where values are consecutive
    to each other. Points to numbafied function _split_consecutive().
    """
    split_array = _split_consecutive(array, stepsize)
    return split_array