コード例 #1
0
    def RRI_Mean(self,
                 nni=None,
                 rpeaks=None,
                 full=True,
                 overlap=False,
                 duration=300):

        # Check input
        nn = tools.check_input(nni, rpeaks)

        # Signal segmentation into 5 min segments
        segments, seg = tools.segmentation(nn,
                                           full=full,
                                           overlap=overlap,
                                           duration=duration)
        if seg:
            rri_mean_ = statistics.mean([np.mean(x) for x in segments])

        else:
            rri_mean_ = float('nan')
            if tools.WARN:
                warnings.warn(
                    "Signal duration too short for RRI Mean computation.")

        # Output
        args = [rri_mean_]
        names = ['rri_mean']
        return utils.ReturnTuple(args, names)
コード例 #2
0
def get_class_targets_idx(y_train):
    """ Indexes location for the different class labels.

        Parameters
        ----------
        y_train : array
            Training set class labels.

        Returns
        -------
        class_labels_idx : array
            Indexes location for the diffrent class labels.

    """
    class_labels_idx = []
    for i in range(len(np.unique(y_train))):
        _labels_idx = []
        for idx, lab_sample in enumerate(y_train):
            if lab_sample == (i+1):
                _labels_idx += [idx]
        class_labels_idx.append(_labels_idx)

    args = (class_labels_idx, )
    names = ('class_labels_idx', )

    return utils.ReturnTuple(args, names)
コード例 #3
0
    def sdann(self,
              nni=None,
              rpeaks=None,
              full=True,
              overlap=False,
              duration=300):

        # Check input
        nn = tools.check_input(nni, rpeaks)

        # Signal segmentation into 5 min segments
        segments, seg = tools.segmentation(nn,
                                           full=full,
                                           overlap=overlap,
                                           duration=duration)

        if seg:
            mean_values = [np.mean(x) for x in segments]
            sdann_ = tools.std(mean_values)
        else:
            sdann_ = float('nan')
            if tools.WARN:
                warnings.warn(
                    "Signal duration too short for SDANN computation.")

        # Output
        args = [sdann_]
        names = ['sdann']
        return utils.ReturnTuple(args, names)
コード例 #4
0
ファイル: ECGFeatures.py プロジェクト: gitarja/ValenceArousal
    def lyapunov_exponent(self, nni=None, rpeaks=None, emb_dim=10, matrix_dim=4):
            """
            Computes Lyapunov Exponent for of the NNi series
            The first LE is considered as the instantaneous dominant of LE
            Recommendations for parameter settings by Eckmann et al.:
                - long recording time improves accuracy, small tau does not
                - Use large values for emb_dim
                - Matrix_dim should be ‘somewhat larger than the expected number of positive Lyapunov exponents’
                - Min_nb = min(2 * matrix_dim, matrix_dim + 4)
            :param nni:
            :param rpeaks:
            :param emb_dim:
            :param matrix_dim:expected dimension of lyapunov exponential
            :return: the first LE
            """
            min_nb = min(2 * matrix_dim, matrix_dim + 4)

            # Check input values
            nn = pyhrv.utils.check_input(nni, rpeaks)

            # compute Lyapunov Exponential
            lyapex = nolds.lyap_e(data=nn, emb_dim=emb_dim, matrix_dim=matrix_dim, min_nb=min_nb)

            # Output
            args = (lyapex[0],)
            names = ('lyapex',)
            return utils.ReturnTuple(args, names)
コード例 #5
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def sdnn(nni=None, rpeaks=None):
    """Computation of the standard deviation of an NN interval series.

	References: [Electrophysiology1996]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#sdnn-sdnn

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	sdnn : float
		Standard deviation of NN intervals [ms].

	Notes
	-----
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Computation of SDNN & Output
    args = [tools.std(nn)]
    names = ['sdnn']
    return utils.ReturnTuple(args, names)
コード例 #6
0
    def _check_freq_bands(self, freq_bands):
        if freq_bands is None:
            # Set default values
            ulf = None
            vlf = (0.000, 0.04)
            lf = (0.04, 0.15)
            hf = (0.15, 0.4)
            args = (ulf, vlf, lf, hf)
            names = ('ulf', 'vlf', 'lf', 'hf')
        else:
            # Check available data
            args_ = []
            names_ = []

            # ULF band
            ulf = freq_bands['ulf'] if 'ulf' in freq_bands.keys() else (0, 0)
            args_.append(ulf)
            names_.append('ulf')

            # VLF band
            vlf = freq_bands['vlf'] if 'vlf' in freq_bands.keys() else (0.003,
                                                                        0.04)
            args_.append(vlf)
            names_.append('vlf')

            # LF band
            lf = freq_bands['lf'] if 'lf' in freq_bands.keys() else (0.04,
                                                                     0.15)
            args_.append(lf)
            names_.append('lf')

            # HF band
            hf = freq_bands['hf'] if 'hf' in freq_bands.keys() else (0.15, 0.4)
            args_.append(hf)
            names_.append('hf')

            # Check if freq_band limits are valid
            # Rule: top frequency of a lower frequency band must not be higher than the lower frequency of a higher
            # frequency band
            invalid = False
            args_ = [list(x) for x in args_ if x is not None]
            for i, val in enumerate(args_[:-1]):
                if val != (0, 0):
                    if args_[i][1] > args_[i + 1][0]:
                        subs = args_[i][1]
                        args_[i][1] = args_[i + 1][0]
                        args_[i + 1][0] = subs
                        invalid = True
                else:
                    args_[i] = None

            if invalid:
                raise ValueError(
                    "Invalid or overlapping frequency band limits.")

            args = args_
            names = names_

        return utils.ReturnTuple(args, names)
コード例 #7
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def sdann(nni=None, rpeaks=None, full=True, overlap=False, duration=300):
    """Computes the standard deviation of the mean NNI value of each segment (default: 300s segments).

	References: [Electrophysiology1996], [Lohninger2017]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#sdann-sdann

	Parameters
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	full : bool, optional
		If True, returns last segment, even if the cumulative sum of NNI does not reach the 300s (default: False).
	overlap : bool, optional
		If True, allow to return NNI that go from the interval of one segment to the successive segment (default: False).
	duration : int, optional
		Maximum duration duration per segment in [s] (default: 300s).

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	sdnn_index : float
		Standard deviations of the means of all NN intervals within 5 minutes intervals in [ms].

	Notes
	-----
	..	Only one type of input data is required
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format
	..	In some cases, the NN interval may start in a segment (or time interval) N and end only in the successive
		segment N+1. In this case, use the 'overlap' parameter to select if the first element of the segment should be
		dropped or not:
		..	If True: overlap allowed, returns all NNI but the cumulative sum of the NNI in a segment can be greater
			than the specified duration.
		..	If False: no overlap allowed, first NNI will be dropped and the cumulative sum of the NNI in a segment
			will always be < specified duration.
	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Signal segmentation into 5 min segments
    segments, seg = tools.segmentation(nn,
                                       full=full,
                                       overlap=overlap,
                                       duration=duration)

    if seg:
        mean_values = [np.mean(x) for x in segments]
        sdann_ = tools.std(mean_values)
    else:
        sdann_ = float('nan')
        if tools.WARN:
            warnings.warn("Signal duration too short for SDANN computation.")

    # Output
    args = [sdann_]
    names = ['sdann']
    return utils.ReturnTuple(args, names)
コード例 #8
0
def PeaksID(signal=None, UQ=None, LQ=None, IQR=None):
    """
	# 2nd Step - Identifying Respiration Cycles by finding its peaks
	# Search of Peaks is computed in 1500 samples (1.5) seconds windows
		
    Parameters
    ----------
    signal : array
        Raw Respiration signal.
    UQ, LQ, IQR :
    	Upper and Lower Quartiles, and InterQuartile Range

    Returns
    -------
	indexPeaks : list
		indices of the Peaks
	peaks : list
		Peaks of the input signal
	"""

    # check inputs
    if signal is None:
        raise TypeError("Please specify an input signal.")

    # ensure numpy
    signal = np.array(signal)

    LQ, UQ, IQR = findQuartiles(signal)

    indexPeaks = []
    lens = len(signal)
    peaks = []
    for t in range(0, len(signal), 1500):
        portion = signal[t:t + 1500]
        localmax = np.abs(portion.max())
        localmin = np.abs(portion.min())

        if localmax > UQ:
            peaks.append(localmax)
            # if last window has less than 1500 samples
            if t + 1500 > lens:
                for t in range(t, lens):
                    if signal[t] == localmax:
                        indexPeaks.append(t)
            else:
                for t in range(t, t + 1500):
                    if signal[t] == localmax:
                        indexPeaks.append(t)
                        break

    if (indexPeaks[0] == 0):
        indexPeaks = indexPeaks[1:]

    # output
    args = (indexPeaks, peaks)
    names = ('indexPeaks', 'peaks')

    return utils.ReturnTuple(args, names)
コード例 #9
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def nnXX(nni=None, rpeaks=None, threshold=None):
    """Find number of NN interval differences greater than a specified threshold and ratio between number of intervals
	> threshold and total number of NN interval differences.

	References:	[Electrophysiology1996], [Ewing1984]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#nnxx-nnxx

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	threshold : int
		Threshold for nnXX values in [ms].

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	nnXX: int
		Number of NN interval differences greater than the specified threshold [-].
	pnnXX : float
		Ratio between nnXX and total number of NN interval differences [-].

	Notes
	-----
	..	Only one type of input data is required
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format
	..	The ``XX`` in the ``nnXX`` and the ``pnnXX`` keys are substituted by the specified threshold (``threshold``).

		For instance, ``nnXX(nni, threshold=30)`` returns the custom ``nn30`` and ``pnn30`` parameters. Using a
		``threshold=30`` as ``nnXX(nni, threshold=35`` returns the custom ``nn35`` and ``pnn35`` parameters.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Check threshold
    if threshold is None:
        raise TypeError(
            "No threshold specified. Please specify a [ms] threshold.")
    if threshold <= 0:
        raise ValueError(
            "Invalid value for 'threshold'. Value must not be <= 0.")

    # Count NN20
    nnd = tools.nni_diff(nn)
    nnxx = sum(i > threshold for i in nnd)
    pnnxx = nnxx / len(nnd) * 100

    # Output
    args = (nnxx, pnnxx)
    names = ('nn%i' % threshold, 'pnn%i' % threshold)
    return utils.ReturnTuple(args, names)
コード例 #10
0
def IRR(signal=None, peaks=None, sampling_rate=1000., physiological_lim=0.45):
    """
	# 3rd Step - Selecting only the physiologically meaningful Respiration Cycles and respective Peaks
				
    Parameters
    ----------
    signal : array
        Raw Respiration signal.
    UQ, LQ, IQR :
    	Upper and Lower Quartiles, and InterQuartile Range
	physiological_lim : float, optional
		Only the IRR values below this physiological threshold are accepted.
		Default is 0.45 
		( no biosppy estava 0.35 - confirmar qual o melhor valor para default, nos dados deste dataset 0.45 pareceu razoavel)

    Returns
    -------
	peaks_corrected : list
		indices of the Peaks after physiological-based selection
	rate : list
		instantaneous respiration rate
	"""

    # Original indices of the peaks
    peaks = PeaksID(signal=data5)['indexPeaks']

    # Instantaneous respiration rate
    rate = sampling_rate * (1. / np.diff(peaks))

    # Indices of peaks w/ physiological meaning
    peaks_corrected = np.asarray(peaks)

    # accepting only the IRR within physiological limits
    indx = np.nonzero(rate <= physiological_lim)
    rate = rate[indx]
    if len(indx[0] > 0):
        newindx = np.append(indx[0], indx[0][-1] + 1)
        peaks_corrected = peaks_corrected[newindx]
    else:
        peaks_corrected = peaks_corrected[indx]

    # get time vectors
    length = len(signal)
    T = (length - 1) / sampling_rate
    ts = np.linspace(0, T, length, endpoint=False)

    # output
    args = (peaks_corrected, rate)
    names = ('peaks_corrected', 'rate')

    return utils.ReturnTuple(args, names)
コード例 #11
0
def sample_entropy(nn=None, rpeaks=None, dim=2, tolerance=None):
    """Computes the sample entropy (sampen) of the NNI series.

	Parameters
	----------
	nn : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	dim : int, optional
		Entropy embedding dimension (default: 2).
	tolerance : int, float, optional
		Tolerance distance for which the vectors to be considered equal (default: std(NNI) * 0.2).

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	sample_entropy : float
		Sample entropy of the NNI series.

	Raises
	------
	TypeError
		If 'tolerance' is no numeric value.

	"""
    # Check input values
    nn = tools.check_input(nn, rpeaks)

    if tolerance is None:
        tolerance = np.std(nn, ddof=-1) * 0.2
    else:
        try:
            tolerance = float(tolerance)
        except:
            raise TypeError(
                'Tolerance level cannot be converted to float.'
                'Please verify that tolerance is a numeric (int or float).')

    # Compute Sample Entropy
    sampen = float(nolds.sampen(nn, dim, tolerance))

    # Output
    args = (sampen, )
    names = ('sampen', )
    return utils.ReturnTuple(args, names)
コード例 #12
0
def join_tuples(*args):
	"""Joins multiple biosppy.utils.ReturnTuple objects into one biosppy.utils.ReturnTuple object.

	Docs:	https://pyhrv.readthedocs.io/en/latest/_pages/api/tools.html#join-tuples-join-tuples

	Parameters
	----------
	tuples : list, array, utils.ReturnTuple objects
		List or array containing utils.ReturnTuple objects.

	Returns
	-------
	return_tuple : biosppy.utils.ReturnTuple object
		biosppy.utils.ReturnTuple object with the content of all input tuples joined together.

	Raises
	------
	TypeError:
		If no input data is provided
	TypeError:
		If input data contains non-biosppy.utils.ReturnTuple objects

	Notes
	----
	..	You can find the documentation for this function here:
		https://pyhrv.readthedocs.io/en/latest/_pages/api/tools.html#join-tuples-join-tuples

	"""
	# Check input
	if args is None:
		raise TypeError("Please specify input data.")

	for i in args:
		if not isinstance(i, utils.ReturnTuple):
			raise TypeError("The list of tuples contains non-utils.ReturnTuple objects.")

	# Join tuples
	names = ()
	vals = ()

	for i in args:
		for key in i.keys():
			names = names + (key, )
			vals = vals + (i[key], )

	return utils.ReturnTuple(vals, names)
コード例 #13
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def nni_differences_parameters(nni=None, rpeaks=None):
    """Computes basic statistical parameters from a series of successive NN interval differences (mean, min, max, standard deviation).

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	nni_diff_mean: float
		Mean NN interval difference [ms].
	nni_diff_min : float
		Minimum NN interval difference [ms].
	nni_diff_max : float
		Maximum NN interval difference [ms].

	Notes
	-----
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Get NN interval differences
    nnd = tools.nni_diff(nn)

    # output
    args = (
        float(nnd.mean()),
        int(nnd.min()),
        int(nnd.max()),
    )
    names = (
        'nni_diff_mean',
        'nni_diff_min',
        'nni_diff_max',
    )
    return utils.ReturnTuple(args, names)
コード例 #14
0
def outlierdetection(signal=None, UQ=None, LQ=None, IQR=None):
    """
	# 1st Step - Outlier Detection
	# outliers' condition: those points that are >= 1.5IQR+UQ or <= LQ-1.5IQR.

	
    Parameters
    ----------
    signal : array
        Raw Respiration signal.
    UQ, LQ, IQR :
    	Upper and Lower Quartiles, and InterQuartile Range

    Returns
    -------
	outliers : list
		outliers indices
	perc_corrupted : float 
		percentage of outliers/corrupted signal

	"""

    # check inputs
    if signal is None:
        raise TypeError("Please specify an input signal.")

    # ensure numpy
    signal = np.array(signal)

    LQ, UQ, IQR = findQuartiles(signal)

    outliers = []

    for q in range(0, len(signal)):
        if signal[q] >= (1.5 * IQR + UQ) or signal[q] <= (LQ - 1.5 * IQR):
            outliers.append(q)

    perc_corrupted = round(float(len(outliers)) / float(len(signal)) * 100, 3)

    # output
    args = (outliers, perc_corrupted)
    names = ('outliers', 'perc_corrupted')

    return utils.ReturnTuple(args, names)
コード例 #15
0
def findQuartiles(signal=None):
    """
	# auxiliar function for the 1st processing step (outlier removal), by using quartiles
	# Since quartiles are less sensitive to spikes that may appear in respiration measurements, those can be used for outlier detection. 
	# Finding the upper quartile (UQ), the lower quartile (LQ), and the interquartile range (IQR) = UQ-LQ. 
    
    Parameters
    ----------
    signal : array
        Raw Respiration signal.

    Returns
    -------
    UQ :
    	Upper Quartile
	LQ :
		Lower Quartile
	IQR :
		InterQuartile Range

	"""

    # check inputs
    if signal is None:
        raise TypeError("Please specify an input signal.")

    # ensure numpy
    signal = np.array(signal)

    #sort the signal
    sortedS = np.sort(signal)

    # find the upper quartile (UQ=Q4) and lower quartile (LQ=Q1)
    Q1 = sortedS[len(signal) / 4]
    Q4 = sortedS[len(signal) * 3 / 4]

    #compute the interquartile range (IQR) = UQ-LQ
    IQR = Q4 - Q1
    # output
    args = (Q1, Q4, IQR)
    names = ('LQ', 'UQ', 'IQR')

    return utils.ReturnTuple(args, names)
コード例 #16
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def hr_parameters(nni=None, rpeaks=None):
    """Computes basic statistical parameters from a series of Heart Rate (HR) data (mean, min, max, standard deviation).

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	hr_mean : float
		Mean heart rate [bpm].
	hr_min : float
		Minimum heart rate value [bpm].
	hr_max : float
		Maximum heart rate value [bpm].
	hr_std : float
		Standard deviation of the HR series [bpm].

	Notes
	-----
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Get heart rate series
    hr = tools.heart_rate(nn)

    # Output
    args = (hr.mean(), hr.min(), hr.max(), hr.std(ddof=1))
    names = ('hr_mean', 'hr_min', 'hr_max', 'hr_std')
    return utils.ReturnTuple(args, names)
コード例 #17
0
def hrv_import(hrv_file=None):
	"""Imports HRV results stored in JSON files generated with the 'hrv_export()' function.

	Docs:	https://pyhrv.readthedocs.io/en/latest/_pages/api/tools.html#hrv-import-hrv-import

	Parameters
	----------
	hrv_file : file object, str
		File handler or absolute string path of the HRV JSON file

	Returns
	-------
	output : biosppy.utils.ReturnTuple object
		All imported results.

	Raises
	------
	TypeError
		No input data provided.

	Notes
	-----
	..	You can find the documentation for this function here:
		https://pyhrv.readthedocs.io/en/latest/_pages/api/tools.html#hrv-import-hrv-import

	"""
	# Check input data and load JSON file content
	if hrv_file is None:
		raise TypeError("No input data provided. Please specify input data.")
	elif type(hrv_file) is str:
		data = json.load(open(hrv_file, 'r'))
	elif isinstance(hrv_file, file):
		data = json.load(hrv_file)

	results = dict()
	for key in data.keys():
		results[str(key)] = data[key] if type(data[key]) is not unicode else str(data[key])

	# Create utils.ReturnTuple object from imported data
	return utils.ReturnTuple(results.values(), results.keys())
コード例 #18
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def rmssd(nni=None, rpeaks=None):
    """Computes root mean of squared differences of successive NN Intervals.

	References: [Electrophysiology1996], [Lohninger2017]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#rmssd-rmssd

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	rmssd : float
		RMSSD value in [ms].

	Notes
	-----
	..	Only one type of input data is required
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Compute RMSSD
    nnd = tools.nni_diff(nn)
    rmssd_ = np.sum(x**2 for x in nnd)
    rmssd_ = np.sqrt(1. / nnd.size * rmssd_)

    # Output
    args = (rmssd_, )
    names = ('rmssd', )
    return utils.ReturnTuple(args, names)
コード例 #19
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def nni_parameters(nni=None, rpeaks=None):
    """Computes basic statistical parameters from a series of NN intervals (# of intervals, mean, min, max).

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	nni_counter : int
		Number of NN intervals.
	nni_mean : float
		Mean NN interval [ms].
	nni_min : float
		Minimum NN interval [ms].
	nni_max : float
		Maximum NN interval [ms].

	Notes
	-----
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'nni' will be chosen over the 'rpeaks'
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # output
    args = (int(nn.size), nn.mean(), nn.min(), nn.max())
    names = ('nni_counter', 'nni_mean', 'nni_min', 'nni_max')
    return utils.ReturnTuple(args, names)
コード例 #20
0
def poincare(nni=None, rpeaks=None, show=True, plot=True, figsize=None, ellipse=True, vectors=True, legend=True, \
                            marker='o'):
    """Creates Poincaré plot from a series of NN intervals or R-peak locations and derives the Poincaré related
	parameters SD1, SD2, SD1/SD2 ratio, and area of the Poincaré ellipse.

	References: [Tayel2015][Brennan2001]

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	show : bool, optional
		If true, shows Poincaré plot (default: True).
	figsize : array, optional
		Matplotlib figure size (width, height) (default: (6, 6)).
	ellipse : bool, optional
		If true, shows fitted ellipse in plot (default: True).
	vectors : bool, optional
		If true, shows SD1 and SD2 vectors in plot (default: True).
	legend : bool, optional
		If True, adds legend to the Poincaré plot (default: True).
	marker : character, optional
		NNI marker in plot (default: 'o').

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	poincare_plot : matplotlib figure object
		Poincaré plot figure
	sd1 : float
		Standard deviation (SD1) of the major axis
	sd2 : float, key: 'sd2'
		Standard deviation (SD2) of the minor axis
	sd_ratio: float
		Ratio between SD2 and SD1 (SD2/SD1)
	ellipse_area : float
		Area of the fitted ellipse

	"""
    # Check input values
    nn = tools.check_input(nni, rpeaks)

    # Prepare Poincaré data
    x1 = np.asarray(nn[:-1])
    x2 = np.asarray(nn[1:])

    # SD1 & SD2 Computation
    sd1 = np.std(np.subtract(x1, x2) / np.sqrt(2))
    sd2 = np.std(np.add(x1, x2) / np.sqrt(2))

    # Area of ellipse
    area = np.pi * sd1 * sd2

    # if plot is wanted
    if plot:
        # Prepare figure
        if figsize is None:
            figsize = (6, 6)
        fig = plt.figure(figsize=figsize)
        fig.tight_layout()
        ax = fig.add_subplot(111)

        ax.set_title(r'$Poincar\acute{e}$')
        ax.set_ylabel('$NNI_{i+1}$ [ms]')
        ax.set_xlabel('$NNI_i$ [ms]')
        ax.set_xlim([np.min(nn) - 50, np.max(nn) + 50])
        ax.set_ylim([np.min(nn) - 50, np.max(nn) + 50])
        ax.grid()
        ax.plot(x1, x2, 'r%s' % marker, markersize=2, alpha=0.5, zorder=3)

        # Compute mean NNI (center of the Poincaré plot)
        nn_mean = np.mean(nn)

        # Draw poincaré ellipse
        if ellipse:
            ellipse_ = mpl.patches.Ellipse((nn_mean, nn_mean),
                                           sd1 * 2,
                                           sd2 * 2,
                                           angle=-45,
                                           fc='k',
                                           zorder=1)
            ax.add_artist(ellipse_)
            ellipse_ = mpl.patches.Ellipse((nn_mean, nn_mean),
                                           sd1 * 2 - 1,
                                           sd2 * 2 - 1,
                                           angle=-45,
                                           fc='lightyellow',
                                           zorder=1)
            ax.add_artist(ellipse_)

        # Add poincaré vectors (SD1 & SD2)
        if vectors:
            arrow_head_size = 3
            na = 4
            a1 = ax.arrow(nn_mean,
                          nn_mean, (-sd1 + na) * np.cos(np.deg2rad(45)),
                          (sd1 - na) * np.sin(np.deg2rad(45)),
                          head_width=arrow_head_size,
                          head_length=arrow_head_size,
                          fc='g',
                          ec='g',
                          zorder=4,
                          linewidth=1.5)
            a2 = ax.arrow(nn_mean,
                          nn_mean, (sd2 - na) * np.cos(np.deg2rad(45)),
                          (sd2 - na) * np.sin(np.deg2rad(45)),
                          head_width=arrow_head_size,
                          head_length=arrow_head_size,
                          fc='b',
                          ec='b',
                          zorder=4,
                          linewidth=1.5)
            a3 = mpl.patches.Patch(facecolor='white', alpha=0.0)
            a4 = mpl.patches.Patch(facecolor='white', alpha=0.0)
            ax.add_line(
                mpl.lines.Line2D((min(nn), max(nn)), (min(nn), max(nn)),
                                 c='b',
                                 ls=':',
                                 alpha=0.6))
            ax.add_line(
                mpl.lines.Line2D((nn_mean - sd1 * np.cos(np.deg2rad(45)) * na,
                                  nn_mean + sd1 * np.cos(np.deg2rad(45)) * na),
                                 (nn_mean + sd1 * np.sin(np.deg2rad(45)) * na,
                                  nn_mean - sd1 * np.sin(np.deg2rad(45)) * na),
                                 c='g',
                                 ls=':',
                                 alpha=0.6))

            # Add legend
            if legend:
                ax.legend([a1, a2, a3, a4], [
                    'SD1: %.3f$ms$' % sd1,
                    'SD2: %.3f$ms$' % sd2,
                    'S: %.3f$ms^2$' % area,
                    'SD1/SD2: %.3f' % (sd1 / sd2)
                ],
                          framealpha=1)

        # Show plot
        if show:
            plt.show()
    else:
        fig = None

    # Output
    args = (fig, sd1, sd2, sd2 / sd1, area)
    names = ('poincare_plot', 'sd1', 'sd2', 'sd_ratio', 'ellipse_area')
    return utils.ReturnTuple(args, names)
コード例 #21
0
def dfa(nn=None,
        rpeaks=None,
        short=None,
        long=None,
        show=True,
        figsize=None,
        legend=True):
    """Conducts Detrended Fluctuation Analysis for short and long-term fluctuation of an NNI series.

	References: [Joshua2008][Kuusela2014][Fred2017]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/nonlinear.html#sample-entropy-sample-entropy

	Parameters
	----------
	nn : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	short : array, 2 elements
		Interval limits of the short term fluctuations (default: None: [4, 16]).
	long : array, 2 elements
		Interval limits of the long term fluctuations (default: None: [17, 64]).
	show : bool
		If True, shows DFA plot (default: True)
	legend : bool
		If True, adds legend with alpha1 and alpha2 values to the DFA plot (default: True)

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	dfa_short : float
		Alpha value of the short term fluctuations
	dfa_long : float
		Alpha value of the long term fluctuations
	dfa_plot : matplotlib plot figure
		Matplotlib plot figure of the DFA

	"""
    # Check input values
    nn = tools.check_input(nn, rpeaks)

    # Check intervals
    short = tools.check_interval(short, default=(4, 16))
    long = tools.check_interval(long, default=(17, 64))

    # Create arrays
    short = range(short[0], short[1] + 1)
    long = range(long[0], long[1] + 1)

    # Prepare plot
    if figsize is None:
        figsize = (6, 6)
    fig = plt.figure(figsize=figsize)
    ax = fig.add_subplot(111)
    ax.set_title('Detrended Fluctuation Analysis (DFA)')
    ax.set_xlabel('log n [beats]')
    ax.set_ylabel('log F(n)')

    # try:
    # Compute alpha values
    try:
        alpha1, dfa_short = nolds.dfa(nn,
                                      short,
                                      debug_data=True,
                                      overlap=False)
        alpha2, dfa_long = nolds.dfa(nn, long, debug_data=True, overlap=False)
    except ValueError:
        # If DFA could not be conducted due to insufficient number of NNIs, return an empty graph and 'nan' for alpha1/2
        warnings.warn(
            "Not enough NNI samples for Detrended Fluctuations Analysis.")
        ax.axis([0, 1, 0, 1])
        ax.text(0.5,
                0.5,
                '[Insufficient number of NNI samples for DFA]',
                horizontalalignment='center',
                verticalalignment='center')
        alpha1, alpha2 = 'nan', 'nan'
    else:
        # Plot DFA results if number of NNI were sufficent to conduct DFA
        # Plot short term DFA
        vals, flucts, poly = dfa_short[0], dfa_short[1], np.polyval(
            dfa_short[2], dfa_short[0])
        label = r'$ \alpha_{1}: %0.2f$' % alpha1
        ax.plot(vals, flucts, 'bo', markersize=1)
        ax.plot(vals, poly, 'b', label=label, alpha=0.7)

        # Plot long term DFA
        vals, flucts, poly = dfa_long[0], dfa_long[1], np.polyval(
            dfa_long[2], dfa_long[0])
        label = r'$ \alpha_{2}: %0.2f$' % alpha2
        ax.plot(vals, flucts, 'go', markersize=1)
        ax.plot(vals, poly, 'g', label=label, alpha=0.7)

        # Add legend
        if legend:
            ax.legend()
        ax.grid()

    # Plot axis
    if show:
        plt.show()

    # Output
    args = (
        fig,
        alpha1,
        alpha2,
    )
    return utils.ReturnTuple(args, (
        'dfa_plot',
        'dfa_alpha1',
        'dfa_alpha2',
    ))
コード例 #22
0
def lomb_psd(
		nni=None,
		rpeaks=None,
		fbands=None,
		nfft=2**8,
		ma_size=None,
		show=True,
		show_param=True,
		legend=True,
		mode='normal'
	):
	"""Computes a Power Spectral Density (PSD) estimation from the NNI series using the Lomb-Scargle Periodogram
	and computes all frequency domain parameters from this PSD according to the specified frequency bands.

	References: [Lomb1976], [Scargle1982], [Kuusela2014], [Laguna1995]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/frequency.html#lomb-scargle-periodogram-lomb-psd

	Parameters
	----------
	rpeaks : array
		R-peak locations in [ms] or [s]
	nni : array
		NN-Intervals in [ms] or [s]
	fbands : dict, optional
		Dictionary with frequency bands (2-element tuples or list)
		Value format:	(lower_freq_band_boundary, upper_freq_band_boundary)
		Keys:	'ulf'	Ultra low frequency		(default: none) optional
				'vlf'	Very low frequency		(default: (0.003Hz, 0.04Hz))
				'lf'	Low frequency			(default: (0.04Hz - 0.15Hz))
				'hf'	High frequency			(default: (0.15Hz - 0.4Hz))´
	nfft : int, optional
		Number of points computed for the FFT result (default: 2**8)
	ma_size : int, optional
		Window size of the optional moving average filter (default: None)
	show : bool, optional
		If true, show PSD plot (default: True)
	show_param : bool, optional
		If true, list all computed PSD parameters next to the plot (default: True)
	legend : bool, optional
		If true, add a legend with frequency bands to the plot (default: True)

	Returns
	-------
	results : biosppy.utils.ReturnTuple object
		All results of the Lomb-Scargle PSD estimation (see list and keys below)

	Returned Parameters & Keys
	--------------------------
	..	Peak frequencies of all frequency bands in [Hz] (key: 'lomb_peak')
	..	Absolute powers of all frequency bands in [ms^2][(key: 'lomb_abs')
	..	Relative powers of all frequency bands [%] (key: 'lomb_rel')
	..	Logarithmic powers of all frequency bands [-] (key: 'lomb_log')
	..	Normalized powers of all frequency bands [-] (key: 'lomb_norms')
	..	LF/HF ratio [-] (key: 'lomb_ratio')
	..	Total power over all frequency bands in [ms^2] (key: 'lomb_total')
	.. 	Number of PSD samples (key: 'lomb_nfft')
	.. 	Moving average filter order (key: 'lomb_ma')

	Notes
	-----
	..	The returned BioSPPy ReturnTuple object contains all frequency band parameters in parameter specific tuples
		of length 4 when using the ULF frequency band or of length 3 when NOT using the ULF frequency band.
		The structures of those tuples are shown in this example below (lomb_results = ReturnTuple object returned by
		this function):

			Using ULF, VLF, LF and HF frequency bands:
				lomb['fft_peak'] = (ulf_peak, vlf_peak, lf_peak, hf_peak)

			Using VLF, LF and HF frequency bands:
				lomb['fft_peak'] = (vlf_peak, lf_peak, hf_peak)

	..	If 'show_param' is true, the parameters (incl. frequency band limits) will be listed next to the graph and no
		legend with frequency band limits will be added to the plot graph itself, i.e. the effect of 'show_param'
		will be used over the 'legend' effect.
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'rpeaks' will be chosen over the 'nni' and the 'nni' data will be computed
		from the 'rpeaks'.
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
	# Check input
	nn = tools.check_input(nni, rpeaks)

	# Verify or set default frequency bands
	fbands = _check_freq_bands(fbands)
	t = np.cumsum(nn)
	t -= t[0]

	# Compute PSD according to the Lomb-Scargle method
	# Specify frequency grid
	frequencies = np.linspace(0, 0.41, nfft)
	# Compute angular frequencies
	a_frequencies = np.asarray(2 * np.pi / frequencies)
	powers = np.asarray(lombscargle(t, nn, a_frequencies, normalize=True))

	# Fix power = inf at f=0
	powers[0] = 2

	# Apply moving average filter
	if ma_size is not None:
		powers = biosppy.signals.tools.smoother(powers, size=ma_size)['signal']

	# Define metadata
	meta = utils.ReturnTuple((nfft, ma_size, ), ('lomb_nfft', 'lomb_ma'))

	if mode not in ['normal', 'dev', 'devplot']:
		warnings.warn("Unknown mode '%s'. Will proceed with 'normal' mode." % mode, stacklevel=2)
		mode = 'normal'

	# Normal Mode:
	# Returns frequency parameters, PSD plot figure and no frequency & power series/arrays
	if mode == 'normal':
		# ms^2 to s^2
		powers = powers * 10 ** 6

		# Compute frequency parameters
		params, freq_i = _compute_parameters('lomb', frequencies, powers, fbands)

		# Plot parameters
		figure = _plot_psd('lomb', frequencies, powers, freq_i, params, show, show_param, legend)
		figure = utils.ReturnTuple((figure, ), ('lomb_plot', ))

		# Complete output
		return tools.join_tuples(params, figure, meta)

	# Dev Mode:
	# Returns frequency parameters and frequency & power series/array; does not create a plot figure nor plot the data
	elif mode == 'dev':
		# Compute frequency parameters
		params, _ = _compute_parameters('lomb', frequencies, powers, fbands)

		# Complete output
		return tools.join_tuples(params, meta), frequencies, powers

	# Devplot Mode:
	# Returns frequency parameters, PSD plot figure, and frequency & power series/arrays
	elif mode == 'devplot':
		# ms^2 to s^2
		powers = powers * 10**6

		# Compute frequency parameters
		params, freq_i = _compute_parameters('lomb', frequencies, powers, fbands)

		# Plot parameters
		figure = _plot_psd('lomb', frequencies, powers, freq_i, params, show, show_param, legend)
		figure = utils.ReturnTuple((figure, ), ('lomb_plot', ))

		# Complete output
		return tools.join_tuples(params, figure, meta), frequencies, powers
コード例 #23
0
def ar_psd(nni=None,
		   rpeaks=None,
		   fbands=None,
		   nfft=2**12,
		   order=16,
		   show=True,
		   show_param=True,
		   legend=True,
		   mode='normal'):
	"""Computes a Power Spectral Density (PSD) estimation from the NNI series using the Autoregressive method
	and computes all frequency domain parameters from this PSD according to the specified frequency bands.

	References: [Electrophysiology1996], [Kuusela2014], [Kallas2012], [Boardman2002]
				(additionally recommended: [Miranda2012])
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/frequency.html#autoregressive-method-ar-psd

	Parameters
	----------
	rpeaks : array
		R-peak locations in [ms] or [s]
	nni : array
		NN-Intervals in [ms] or [s]
	fbands : dict, optional
		Dictionary with frequency bands (2-element tuples or list)
		Value format:	(lower_freq_band_boundary, upper_freq_band_boundary)
		Keys:	'ulf'	Ultra low frequency		(default: none) optional
				'vlf'	Very low frequency		(default: (0.003Hz, 0.04Hz))
				'lf'	Low frequency			(default: (0.04Hz - 0.15Hz))
				'hf'	High frequency			(default: (0.15Hz - 0.4Hz))´
	nfft : int, optional
		Number of points computed for the entire AR result (default: 2**12)
	order : int, optional
		Autoregressive model order (default: 16)
	show : bool, optional
		If true, show PSD plot (default: True)
	show_param : bool, optional
		If true, list all computed PSD parameters next to the plot (default: True)
	legend : bool, optional
		If true, add a legend with frequency bands to the plot (default: True)

	Returns
	-------
	results : biosppy.utils.ReturnTuple object
		All results of the Autoregressive PSD estimation (see list and keys below)

	Returned Parameters & Keys
	--------------------------
	..	Peak frequencies of all frequency bands in [Hz] (key: 'ar_peak')
	..	Absolute powers of all frequency bands in [ms^2][(key: 'ar_abs')
	..	Relative powers of all frequency bands [%] (key: 'ar_rel')
	..	Logarithmic powers of all frequency bands [-] (key: 'ar_log')
	..	Normalized powers of all frequency bands [-] (key: 'ar_norms')
	..	LF/HF ratio [-] (key: 'ar_ratio')
	..	Total power over all frequency bands in [ms^2] (key: 'ar_total')
	..	Interpolation method (key: 'ar_interpolation')
	..	Resampling frequency (key: 'ar_resampling_frequency')
	.. 	AR model order (key: 'ar_order')
	.. 	Number of PSD samples (key: 'ar_nfft')

	Notes
	-----
	..	The returned BioSPPy ReturnTuple object contains all frequency band parameters in parameter specific tuples
		of length 4 when using the ULF frequency band or of length 3 when NOT using the ULF frequency band.
		The structures of those tuples are shown in this example below (lomb_results = ReturnTuple object returned by
		this function):

			Using ULF, VLF, LF and HF frequency bands:
				lomb['ar_peak'] = (ulf_peak, vlf_peak, lf_peak, hf_peak)

			Using VLF, LF and HF frequency bands:
				lomb['ar_peak'] = (vlf_peak, lf_peak, hf_peak)

	..	If 'show_param' is true, the parameters (incl. frequency band limits) will be listed next to the graph and no
		legend with frequency band limits will be added to the plot graph itself, i.e. the effect of 'show_param'
		will be used over the 'legend' effect.
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'rpeaks' will be chosen over the 'nni' and the 'nni' data will be computed
		from the 'rpeaks'.
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
	# Check input
	nn = tools.check_input(nni, rpeaks)

	# Verify or set default frequency bands
	fbands = _check_freq_bands(fbands)

	# Resampling (with 4Hz) and interpolate
	# Because RRi are unevenly spaced we must interpolate it for accurate PSD estimation.
	fs = 4
	t = np.cumsum(nn)
	t -= t[0]
	f_interpol = sp.interpolate.interp1d(t, nn, 'cubic')
	t_interpol = np.arange(t[0], t[-1], 1000./fs)
	nn_interpol = f_interpol(t_interpol)

	# Compute autoregressive PSD
	ar = spectrum.pyule(data=nn_interpol, order=order, NFFT=nfft, sampling=fs, scale_by_freq=False)
	ar()

	# Get frequencies and powers
	frequencies = np.asarray(ar.frequencies())
	psd = np.asarray(ar.psd)
	powers = np.asarray(10 * np.log10(psd) * 10**3) 	# * 10**3 to compensate with ms^2 to s^2 conversion
														# in the upcoming steps

	# Define metadata
	meta = utils.ReturnTuple((nfft, order, fs, 'cubic'), ('ar_nfft', 'ar_order', 'ar_resampling_frequency',
														  'ar_interpolation'))

	if mode not in ['normal', 'dev', 'devplot']:
		warnings.warn("Unknown mode '%s'. Will proceed with 'normal' mode." % mode, stacklevel=2)
		mode = 'normal'

	# Normal Mode:
	# Returns frequency parameters, PSD plot figure and no frequency & power series/arrays
	if mode == 'normal':
		# Compute frequency parameters
		params, freq_i = _compute_parameters('ar', frequencies, powers, fbands)

		# Plot PSD
		figure = _plot_psd('ar', frequencies, powers, freq_i, params, show, show_param, legend)
		figure = utils.ReturnTuple((figure, ), ('ar_plot', ))

		# Complete output
		return tools.join_tuples(params, figure)

	# Dev Mode:
	# Returns frequency parameters and frequency & power series/array; does not create a plot figure nor plot the data
	elif mode == 'dev':
		# Compute frequency parameters
		params, _ = _compute_parameters('ar', frequencies, powers, fbands)

		# Output
		return tools.join_tuples(params, meta), frequencies, (powers / 10 ** 6)

	# Devplot Mode:
	# Returns frequency parameters, PSD plot figure, and frequency & power series/arrays
	elif mode == 'devplot':
		# Compute frequency parameters
		params, freq_i = _compute_parameters('ar', frequencies, powers, fbands)

		# Plot PSD
		figure = _plot_psd('ar', frequencies, powers, freq_i, params, show, show_param, legend)
		figure = utils.ReturnTuple((figure, ), ('ar_plot', ))

		# Complete output
		return tools.join_tuples(params, figure, meta), frequencies, (powers / 10 ** 6)
コード例 #24
0
def _compute_parameters(method, frequencies, power, freq_bands):
	"""Computes PSD HRV parameters from the PSD frequencies and powers.

	References: [Electrophysiology1996], [Basak2014]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/frequency.html#frequency-parameters

	Parameters
	----------
	method : str
		Method identifier ('fft', 'ar', 'lomb')
	frequencies
		Series of frequencies of the power spectral density computation.
	power : array
		Series of power-values of the power spectral density computation.
	freq_indices : array
		Indices of the frequency samples within each frequency band.
	freq_bands : dict, optional
		Dictionary with frequency bands (tuples or list).
		Value format:	(lower_freq_band_boundary, upper_freq_band_boundary)
		Keys:	'ulf'	Ultra low frequency		(default: none) optional
				'vlf'	Very low frequency		(default: (0.003Hz, 0.04Hz))
				'lf'	Low frequency			(default: (0.04Hz - 0.15Hz))
				'hf'	High frequency			(default: (0.15Hz - 0.4Hz))

	Returns
	-------
	results : biosppy.utils.ReturnTuple object
		All results of the Lomb-Scargle PSD estimation (see list and keys below)

	Returned Parameters & Keys
	--------------------------
	(below, X = method identifier 'fft', 'ar' or 'lomb'
	..	Peak frequencies of all frequency bands in [Hz] (key: 'X_peak')
	..	Absolute powers of all frequency bands in [ms^2] (key: 'X_abs')
	..	Relative powers of all frequency bands in [%] (key: 'X_rel')
	..	Logarithmic powers of all frequency bands [-] (key: 'X_log')
	..	Normalized powers of all frequency bands [-](key: 'X_norms')
	..	LF/HF ratio [–] (key: 'X_ratio')
	..	Total power over all frequency bands in [ms^] (key: 'X_total')

	Raises
	------
	ValueError
		If parameter computation could not be made due to the lack of PSD samples ('nfft' too low)

	"""
	# Compute frequency resolution
	df = (frequencies[1] - frequencies[0])

	# Get indices of freq values within the specified freq bands
	ulf_i, vlf_i, lf_i, hf_i = _get_frequency_indices(frequencies, freq_bands)
	ulf_f, vlf_f, lf_f, hf_f = _get_frequency_arrays(frequencies, ulf_i, vlf_i, lf_i, hf_i)

	# Absolute powers
	if freq_bands['ulf'] is not None:
		ulf_power = np.sum(power[ulf_i]) * df
	vlf_power = np.sum(power[vlf_i]) * df
	lf_power = np.sum(power[lf_i]) * df
	hf_power = np.sum(power[hf_i]) * df
	abs_powers = (vlf_power, lf_power, hf_power, ) if freq_bands['ulf'] is None else (ulf_power, vlf_power, lf_power,
																					  hf_power,)
	total_power = np.sum(abs_powers)

	# Peak frequencies
	if freq_bands['ulf'] is not None:
		ulf_peak = ulf_f[np.argmax(power[ulf_i])]

	# Compute Peak values and catch exception caused if the number of PSD samples is too low
	try:
		vlf_peak = vlf_f[np.argmax(power[vlf_i])]
		lf_peak = lf_f[np.argmax(power[lf_i])]
		hf_peak = hf_f[np.argmax(power[hf_i])]
		peaks = (vlf_peak, lf_peak, hf_peak,) if freq_bands['ulf'] is None else (ulf_peak, vlf_peak, lf_peak, hf_peak,)
	except ValueError as e:
		if 'argmax of an empty sequence' in str(e):
			raise ValueError("'nfft' is too low: not enough PSD samples to compute the frequency parameters. Try to "
							 "increase 'nfft' to avoid this error.")

	# Relative, logarithmic powers & LF/HF ratio
	rels = tuple([float(x) / total_power * 100 for x in abs_powers])
	logs = tuple([float(np.log(x)) for x in abs_powers])
	ratio = float(lf_power) / hf_power

	# Normalized powers
	norms = tuple([100 * x / (lf_power + hf_power) for x in [lf_power, hf_power]])

	# Prepare parameters for plot
	args = (freq_bands, peaks, abs_powers, rels, logs, norms, ratio, total_power)
	names = (
		'%s_bands' % method, '%s_peak' % method, '%s_abs' % method,
		'%s_rel' % method, '%s_log' % method, '%s_norm' % method,
		'%s_ratio' % method, '%s_total' % method)

	# Output
	params = utils.ReturnTuple(args, names)
	freq_i = utils.ReturnTuple((ulf_i, vlf_i, lf_i, hf_i), ('ulf', 'vlf', 'lf', 'hf'))
	return params, freq_i
コード例 #25
0
ファイル: hrv.py プロジェクト: yingding/pyhrv
def hrv(nni=None,
		rpeaks=None,
		signal=None,
		sampling_rate=1000.,
		interval=[0, 10],
		plot_ecg=True,
		plot_tachogram=True,
		show=False,
		fbands=None,
		kwargs_ecg_plot={},
		kwargs_tachogram=None,
		kwargs_time=None,
		kwargs_nonlinear=None,
		kwargs_welch=None,
		kwargs_lomb=None,
		kwargs_ar=None):
	"""Computes all HRV parameters of the pyHRV toolkit (see list below).

	References:	See 'references.txt' for the full list of references

	Parameters
	----------
	nni : array
		NN intervals in (ms) or (s).
	rpeaks : array
		R-peak times in (ms) or (s).
	signal : array
		ECG signal.
	sampling_rate : int, float
		Sampling rate used for the ECG acquisition in (Hz).
	plot_ecg : bool, optional
		If True, plots ECG signal with specified interval ('signal' must not be None).
	plot_tachogram : bool, optional
		If True, plots tachogram with specified interval.
	fbands : dict, optional
		Dictionary with frequency bands for frequency domain (tuples or list).
		Value format:	(lower_freq_band_boundary, upper_freq_band_boundary)
		Keys:	'ulf'	Ultra low frequency		(default: none) optional
				'vlf'	Very low frequency		(default: (0.003Hz, 0.04Hz))
				'lf'	Low frequency			(default: (0.04Hz - 0.15Hz))
				'hf'	High frequency			(default: (0.15Hz - 0.4Hz))
	show : bool, optional
		If true, shows all plots (default: True).

	kwargs_ecg_plot : dict, optional
		**kwargs for the plot_ecg() function (see 'tools.py' module):
			..	rpeaks : bool, optional
					If True, marks R-peaks in ECG signal (default: True).
			..	title : str, optional
					Plot figure title (default: None).

	kwargs_tachogram : dict, optional
		**kwargs for the plot_tachogram() function (see 'tools.py' module):
			..	hr : bool, optional
					If True, plots series of heart rate data in [bpm] (default: True).
			..	title : str, optional
					Plot figure title (default: None).

	kwargs_time : dict, optional
		**kwargs for the time_domain() function (see 'time_domain()' function)
			..	threshold : int, optional
					Custom threshold in [ms] for the NNXX and pNNXX parameters (default: None).
			..	plot : bool
					If True, creates histogram plot using matplotlib, else uses numpy (data only, no plot) -
					(geometrical params).
			..	binsize : int, float
					Bin size in [ms] of the histogram bins - (geometrical params).

	kwargs_welch : dict, optional
		**kwargs for the 'welch_psd()' function:
			..	nfft : int, optional
					Number of points computed for the FFT result (default: 2**12).
			..	detrend : bool optional
					If True, detrend NNI series by subtracting the mean NNI (default: True).
			..	window : scipy window function, optional
					Window function used for PSD estimation (default: 'hamming').

	kwargs_lomb : dict, optional
		**kwargs for the 'lomb_psd()' function:
			..	nfft : int, optional
					Number of points computed for the FFT result (default: 2**8).
			..	ma_size : int, optional
					Window size of the optional moving average filter (default: None).

	kwargs_ar : dict, optional
		**kwargs for the 'ar_psd()' function:
			..	nfft : int, optional
					Number of points computed for the entire AR result (default: 2**12).
			..	order : int, optional
					Autoregressive model order (default: 16).

	kwargs_nonlinear : dict, optional
		**kwargs for the nonlinear functions (poincare(), sample_enntropy(), dfa()):
			..	ellipse : bool, optional
					If true, shows fitted ellipse in plot (default: True).
			..	vectors : bool, optional
					If true, shows SD1 and SD2 vectors in plot (default: True).
			..	legend : bool, optional
					If True, adds legend to the Poincaré plot (default: True).
			..	marker : character, optional
					NNI marker in plot (default: 'o').
			..	short : array, 2 elements
					Interval limits of the short term fluctuations (default: None: [4, 16]).
			..	long : array, 2 elements
					Interval limits of the long term fluctuations (default: None: [17, 64]).
			..	legend : bool
					If True, adds legend with alpha1 and alpha2 values to the DFA plot (default: True).
			..	dim : int, optional
					Entropy embedding dimension (default: 2).
			..	tolerance : int, float, optional
					Tolerance distance for which the vectors to be considered equal (default: std(NNI) * 0.2).

	Returns
	-------
	results : biosppy.utils.ReturnTuple object
		All time domain results.

	Returned Parameters - Time Domain
	---------------------------------
	..	NNI parameters (# of NNI, mean, min, max) in [count] and [ms] (keys: 'nni_counter', 'nni_mean', 'nni_min',
		'nni_max')
	..	NNI differences (mean, min, max, standard deviation) in [ms] (keys: 'nni_diff_mean', 'nni_diff_min',
		'nn_diff_max')
	..	HR parameters (mean, min, max, standard deviation) in [BPM] (keys: 'hr_mean', 'hr_min', 'hr_max', 'hr_std')
	..	SDNN in [ms] (key: 'sdnn')
	..	SDNN index in [ms] (key: 'sdnn_index')
	..	SDANN in [ms] (key: 'sdann')
	..	RMSSD in [ms] (key: 'rmssd')
	..	SDSD in [ms] (key: 'sdsd')
	..	nn50 in [count] & pNN50 in [%] (keys: 'nn50', 'pnn50')
	..	nn20 in [count] & pNN20 in [%] (keys: 'nn20', 'pnn20')
	..	nnXX (XX = custom threshold) if specified (keys: 'nnXX', 'pnnXX')
	..	Triangular Index [-] (key: 'tri_index')
	.. 	TINN in [ms] (key: 'tinn', 'tinn_n', 'tinn_m')

	Returned Parameters - Frequency Domain
	--------------------------------------
	(below, X = one of the methods 'fft' or 'lomb')
	..	Peak frequencies of all frequency bands in [Hz] (key: 'X_peak')
	..	Absolute powers of all frequency bands in [ms^2] (key: 'X_abs')
	..	Relative powers of all frequency bands in [%] (key: 'X_rel')
	..	Logarithmic powers of all frequency bands [-] (key: 'X_log')
	..	Normalized powers of the LF and HF frequency bands [-] (key: 'X_norms')
	..	LF/HF ratio [-] (key: 'X_ratio')
	..	Total power over all frequency bands (key: 'X_total')
	..	Interpolation method used for NNI interpolation (FFT/Welch's method only) (key: 'fft_interpolation')
	..	Resampling frequency used for NNI interpolation (FFT/Welch's method only) (key: 'fft_resampling_frequency')
	..	Spectral window used for PSD estimation of the Welch's method
		(key: 'fft_spectral_window)'

	Returned Parameters - Nonlinear
	-------------------------------
	..	SD1	in [ms] (key: 'sd1')
	..	SD2 in [ms] (key: 'sd2')
	..	SD2/SD1 [-] (key: 'sd_ratio')
	..	Area of the fitted ellipse in [ms^2] (key: 'ellipse_area')
	..	Sample Entropy [-] (key: 'sampen')
	..	Detrended Fluctuations Analysis [-] (short and long term fluctuations) (key: 'dfa_short', 'dfa_long')

	Returned Figures
	----------------
	..	ECG plot (key: 'ecg_plot') (only if ECG signal is provided)
	..	Tachogram (key: 'tachogram_plot')
	..	Poincaré plot (key: 'poincare_plot')
	..	NNI Histogram (key: 'nn_histogram')
	..	Welch PSD (key: 'fft_plot')
	..	Lomb PSD (key: 'lomb_plot')
	..	AR PSD (key: 'ar_plot')
	.. 	Poincaré (key: 'pincare_plot')

	Notes
	-----
	..	Results are stored in a biosppy.utils.ReturnTuple object and need to be accessed with the respective keys as
		done with dictionaries (see list of parameters and keys above).
	..	Provide at least one type of input data (ecg_signal, nn, or rpeaks).
	..	Input data will be prioritized in the following order: 1. ecg_signal, 2. nn, 3. rpeaks.
	..	SDNN Index and SDANN: In some cases, the NN interval may start in a segment (or time interval) N and end only
		in the successive segment N+1. In this case, use the 'overlap' parameter to select if the first element of the
		segment should be dropped or not:
		..	If True: overlap allowed, returns all NNI but the cumulative sum of the NNI in a segment can be greater
			than the specified duration.
		..	If False: no overlap allowed, first NNI will be dropped and the cumulative sum of the NNI in a segment
			will always be < specified duration.

	Raises
	------
	TypeError
		If no input data for 'signal', 'nn' and 'rpeaks' provided.

	"""
	# Check input
	if signal is not None:
		signal, rpeaks = biosppy.signals.ecg.ecg(signal=signal, sampling_rate=sampling_rate, show=False)[1:3]
	elif nni is None and rpeaks is None:
		raise TypeError('No input data provided. Please specify input data.')

	nn = tools.check_input(nni, rpeaks)

	version = utils.ReturnTuple(('v.' + pyhrv.__version__, ), ('version', ))

	# COMPUTE TIME DOMAIN PARAMETERS
	# Check for kwargs for the 'kwargs_time'
	if kwargs_time is not None:
		if type(kwargs_time) is not dict:
			raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_time' must be a dictionary containing "
							"parameters (keys) and values for the 'time_domain()' function." % type(kwargs_time))

		# Supported kwargs
		available_kwargs = ['threshold', 'binsize', 'plot']

		# Unwrwap kwargs dictionary
		threshold = kwargs_time['threshold'] if 'threshold' in kwargs_time.keys() else None
		binsize = kwargs_time['binsize'] if 'binsize' in kwargs_time.keys() else 7.8125
		plot = kwargs_time['plot'] if 'plot' in kwargs_time.keys() else True

		unsupported_kwargs = []
		for args in kwargs_time.keys():
			if args not in available_kwargs:
				unsupported_kwargs.append(args)

		# Throw warning if additional unsupported kwargs have been provided
		if unsupported_kwargs:
			warnings.warn("Unknown kwargs for 'time_domain()': %s. These kwargs have no effect."
						  % unsupported_kwargs, stacklevel=2)

		# Compute Time Domain Parameters
		t_results = td.time_domain(nni=nn, show=False, threshold=threshold, plot=plot)

	else:
		# Compute Welch's PSD with default values
		t_results = td.time_domain(nni=nn, show=False)

	# COMPUTE FREQUENCY DOMAIN RESULTS (kwargs are verified by the frequency_domain() function)
	f_results = fd.frequency_domain(nni=nn, fbands=fbands, kwargs_welch=kwargs_welch, kwargs_lomb=kwargs_lomb,
								 kwargs_ar=kwargs_ar, show=False)

	# COMPUTE NONLINEAR PARAMETERS
	if kwargs_nonlinear is not None:
		if type(kwargs_nonlinear) is not dict:
			raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_nonlinear' must be a dictionary containing "
							"parameters (keys) and values for the 'nonlinear()' function." % type(kwargs_time))

		# Supported kwargs
		available_kwargs = ['ellipse', 'vectors', 'legend', 'marker', 'dim', 'tolerance', 'short', 'long', 'legend']
		kwargs_poincare = {}
		kwargs_sampen = {}
		kwargs_dfa = {}

		# Unwrwap kwargs dictionaries
		kwargs_poincare['ellipse'] = kwargs_nonlinear['ellipse'] if 'ellipse' in kwargs_nonlinear.keys() else True
		kwargs_poincare['vectors'] = kwargs_nonlinear['vectors'] if 'vectors' in kwargs_nonlinear.keys() else True
		kwargs_poincare['legend'] = kwargs_nonlinear['legend'] if 'legend' in kwargs_nonlinear.keys() else True
		kwargs_poincare['marker'] = kwargs_nonlinear['marker'] if 'marker' in kwargs_nonlinear.keys() else 'o'
		kwargs_sampen['dim'] = kwargs_nonlinear['dim'] if 'dim' in kwargs_nonlinear.keys() else 2
		kwargs_sampen['tolerance'] = kwargs_nonlinear['tolerance'] if 'tolerance' in kwargs_nonlinear.keys() else None
		kwargs_dfa['short'] = kwargs_nonlinear['short'] if 'short' in kwargs_nonlinear.keys() else None
		kwargs_dfa['long'] = kwargs_nonlinear['long'] if 'long' in kwargs_nonlinear.keys() else None
		kwargs_dfa['legend'] = kwargs_nonlinear['legend'] if 'legend' in kwargs_nonlinear.keys() else True

		unsupported_kwargs = []
		for args in kwargs_nonlinear.keys():
			if args not in available_kwargs:
				unsupported_kwargs.append(args)

		# Throw warning if additional unsupported kwargs have been provided
		if unsupported_kwargs:
			warnings.warn("Unknown kwargs for 'nonlinear()': %s. These kwargs have no effect."
						  % unsupported_kwargs, stacklevel=2)

		n_results = nl.nonlinear(nni=nn, show=False, kwargs_poincare=kwargs_poincare, kwargs_sampen=kwargs_sampen,
								 kwargs_dfa=kwargs_dfa)
	else:
		n_results = nl.nonlinear(nni=nn, show=False)

	# Prepare output
	results = tools.join_tuples(t_results, f_results, n_results)

	# Plot ECG signal
	if plot_ecg and signal is not None:
		# COMPUTE NONLINEAR PARAMETERS
		if kwargs_ecg_plot is not None:
			if type(kwargs_ecg_plot) is not dict:
				raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_ecg_plot' must be a dictionary containing "
								"parameters (keys) and values for the 'plot_ecg()' function." % type(kwargs_ecg_plot))

			# Supported kwargs
			available_kwargs = ['rpeaks', 'title']

			# Unwrwap kwargs dictionaries
			show_rpeaks = kwargs_ecg_plot['rpeaks'] if 'rpeaks' in kwargs_ecg_plot.keys() else True
			title = kwargs_ecg_plot['title'] if 'title' in kwargs_ecg_plot.keys() else None

			unsupported_kwargs = []
			for args in kwargs_ecg_plot.keys():
				if args not in available_kwargs:
					unsupported_kwargs.append(args)

			# Throw warning if additional unsupported kwargs have been provided
			if unsupported_kwargs:
				warnings.warn("Unknown kwargs for 'plot_ecg()': %s. These kwargs have no effect."
							  % unsupported_kwargs, stacklevel=2)

			ecg_plot = tools.plot_ecg(signal=signal, show=False, rpeaks=show_rpeaks, title=title, interval=interval,
									  sampling_rate=sampling_rate)
		else:
			ecg_plot = tools.plot_ecg(signal=signal, sampling_rate=sampling_rate, show=False, interval=interval)

		results = tools.join_tuples(results, ecg_plot)

	# Plot Tachogram
	if plot_tachogram:
		# COMPUTE NONLINEAR PARAMETERS
		if kwargs_tachogram is not None:
			if type(kwargs_tachogram) is not dict:
				raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_tachogram' must be a dictionary containing "
								"parameters (keys) and values for the 'tachogram()' function." % type(kwargs_tachogram))

			# Supported kwargs
			available_kwargs = ['hr', 'title']

			# Unwrwap kwargs dictionaries
			hr = kwargs_tachogram['hr'] if 'hr' in kwargs_tachogram.keys() else True
			title = kwargs_tachogram['title'] if 'title' in kwargs_tachogram.keys() else None

			unsupported_kwargs = []
			for args in kwargs_tachogram.keys():
				if args not in available_kwargs:
					unsupported_kwargs.append(args)

			# Throw warning if additional unsupported kwargs have been provided
			if unsupported_kwargs:
				warnings.warn("Unknown kwargs for 'tachogram()': %s. These kwargs have no effect."
							  % unsupported_kwargs, stacklevel=2)

			tachogram_plot = tools.tachogram(nni=nn, sampling_rate=sampling_rate, hr=hr, interval=interval,
										title=title, show=False)
		else:
			tachogram_plot = tools.tachogram(nni=nn, show=False, interval=interval)

		results = tools.join_tuples(results, tachogram_plot)

	if show:
		plt.show()

	# Output
	return results
コード例 #26
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def geometrical_parameters(nni=None,
                           rpeaks=None,
                           binsize=7.815,
                           plot=True,
                           show=True,
                           figsize=None,
                           legend=True):
    """Creates NNI histogram with specified binsize (default: 7.815ms) and computes geometrical parameters (triangular
	index, TINN, N, and M).

	References:	[Electrophysiology1996]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#geometrical-parameters-function-geometrical-parameters


	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	binsize : int, float
		Bin size of the histogram bins (default: 7.8125ms).
	plot : bool
		If True, creates histogram plot using matplotlib, else uses numpy (data only, no plot).
	show : bool, optional
		If true, shows histogram (default: True).
	figsize : array, optional
		Matplotlib figure size (width, height) (default: (6, 6)).
	legend : bool, optional
		If True, adds legend to the histogram (default: True).

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	nni_histogram : matplotlib figure object
		Histogram figure (only if input parameter 'plot' is True).
	tri_index : float
		Triangular index.
	tinn_n : float
		N value of the TINN computation.
	tinn_m : float
		M value of the TINN computation.
	tinn : float
		TINN value.

	Raises
	------
	TypeError (via 'check_input()')
		If no input data for 'rpeaks' or 'nni' provided.

	Notes
	-----
	..	Default bin size set to recommended bin size of 1/128 (with 128Hz being the minimum recommended sampling
		frequency) as recommended by the HRV guidelines.
	..	'show' has only effect if 'plot' is also True.
	.. 	'legend' has only effect if 'plot' is also True.
	..	'figsize' has only effect if 'plot' is also True.

	"""

    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Get Histogram data & plot (optional)
    if plot:
        fig, ax, D, bins = _get_histogram(nn,
                                          figsize=figsize,
                                          binsize=binsize,
                                          legend=legend,
                                          plot=plot)
    else:
        fig = None

    # Get TINN values without plot figure
    tinn_vals = tinn(nni=nn,
                     rpeaks=rpeaks,
                     binsize=binsize,
                     show=False,
                     legend=False,
                     figsize=figsize,
                     plot=False)

    # Get triangular index without plot figure
    trindex = triangular_index(nni=nn,
                               rpeaks=rpeaks,
                               binsize=binsize,
                               show=False,
                               legend=False,
                               plot=False)['tri_index']

    # Histogram plot & settings
    if plot:
        # Plot triangular interpolation
        N, M = tinn_vals['tinn_n'], tinn_vals['tinn_m']
        ax.plot([N, bins[np.argmax(D)]], [0, D.max()], 'r--', linewidth=0.8)
        ax.plot([bins[np.argmax(D)], M], [D.max(), 0], 'r--', linewidth=0.8)

        # Add Legend
        if legend:
            l1 = mpl.patches.Patch(facecolor='skyblue',
                                   label='Histogram D(NNI)')
            l2 = mpl.lines.Line2D([0, 0], [0, 0],
                                  linestyle='--',
                                  linewidth=0.8,
                                  color='r',
                                  label='Tri. Interpol.')
            l3 = mpl.patches.Patch(facecolor='g',
                                   alpha=0.0,
                                   label='D(X): %i' % D.max())
            l4 = mpl.patches.Patch(facecolor='g',
                                   alpha=0.0,
                                   label='X: %.3f$ms$' % bins[np.argmax(D)])
            l5 = mpl.patches.Patch(facecolor='white',
                                   alpha=0.0,
                                   label='N: %.3f$ms$' % tinn_vals['tinn_n'])
            l6 = mpl.patches.Patch(facecolor='white',
                                   alpha=0.0,
                                   label='M: %.3fms' % tinn_vals['tinn_m'])
            l7 = mpl.patches.Patch(facecolor='white',
                                   alpha=0.0,
                                   label='TINN: %.3fms' % tinn_vals['tinn'])
            l8 = mpl.patches.Patch(facecolor='white',
                                   alpha=0.0,
                                   label='Tri. Index: %.3f' % trindex)
            ax.legend(handles=[l1, l2, l3, l4, l5, l6, l7, l8], loc=0, ncol=1)

        # Show plot
        if show:
            plt.show()

    # Output
    args = (fig, tinn_vals['tinn_n'], tinn_vals['tinn_m'], tinn_vals['tinn'],
            trindex)
    names = ('nni_histogram', 'tinn_n', 'tinn_m', 'tinn', 'tri_index')
    return utils.ReturnTuple(args, names)
コード例 #27
0
def _check_freq_bands(freq_bands):
	"""Checks provided frequency bands and re-orders the frequency
	boundaries if necessary.

	Example:
		If
			vlf = (0.003, 0.06) and lf = (0.04, 0.15)
		the max frequency of VLF > min frequency of lf (overlapping boundaries).

		Fixed frequency bands:
			vlf = (0.004, 0.04) and lf = (0.06, 0.15)

	Note, that the frequency bands should be reviewed in such case as some intervals will not be taken into the
	parameter computations.

	Parameters
	----------
	freq_bands : dict, optional
		Dictionary with frequency bands (tuples or list).
		Value format:	(lower_freq_band_boundary, upper_freq_band_boundary)
		Keys:	'ulf'	Ultra low frequency		(default: none) optional
				'vlf'	Very low frequency		(default: (0.003Hz, 0.04Hz))
				'lf'	Low frequency			(default: (0.04Hz - 0.15Hz))
				'hf'	High frequency			(default: (0.15Hz - 0.4Hz))
	Returns
	-------
	freq_bands : dict
		Dictionary with valid frequency bands.
	"""
	if freq_bands is None:
		# Set default values
		ulf = None
		vlf = (0.000, 0.04)
		lf = (0.04, 0.15)
		hf = (0.15, 0.4)
		args = (ulf, vlf, lf, hf)
		names = ('ulf', 'vlf', 'lf', 'hf')
	else:
		# Check available data
		args_ = []
		names_ = []

		# ULF band
		ulf = freq_bands['ulf'] if 'ulf' in freq_bands.keys() else (0, 0)
		args_.append(ulf)
		names_.append('ulf')

		# VLF band
		vlf = freq_bands['vlf'] if 'vlf' in freq_bands.keys() else (0.003, 0.04)
		args_.append(vlf)
		names_.append('vlf')

		# LF band
		lf = freq_bands['lf'] if 'lf' in freq_bands.keys() else (0.04, 0.15)
		args_.append(lf)
		names_.append('lf')

		# HF band
		hf = freq_bands['hf'] if 'hf' in freq_bands.keys() else (0.15, 0.4)
		args_.append(hf)
		names_.append('hf')

		# Check if freq_band limits are valid
		# Rule: top frequency of a lower frequency band must not be higher than the lower frequency of a higher
		# frequency band
		invalid = False
		args_ = [list(x) for x in args_ if x is not None]
		for i, val in enumerate(args_[:-1]):
			if val != (0, 0):
				if args_[i][1] > args_[i+1][0]:
					subs = args_[i][1]
					args_[i][1] = args_[i+1][0]
					args_[i+1][0] = subs
					invalid = True
			else:
				args_[i] = None

		if invalid:
			raise ValueError("Invalid or overlapping frequency band limits.")

		args = args_
		names = names_

	return utils.ReturnTuple(args, names)
コード例 #28
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def triangular_index(nni=None,
                     rpeaks=None,
                     binsize=7.8125,
                     plot=True,
                     show=True,
                     figsize=None,
                     legend=True):
    """Computes triangular index based on the NN intervals histogram.

	References:	[Electrophysiology1996]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#triangular-index-triangular-index

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	binsize : int, float
		Bin size of the histogram bins (default: 7.8125ms).
	plot : bool
		If True, creates histogram plot using matplotlib, else uses numpy (data only, no plot).
	show : bool, optional
		If true, shows histogram (default: True).
	figsize : array, optional
		Matplotlib figure size (width, height) (default: (6, 6)).
	legend : bool, optional
		If True, adds legend to the histogram (default: True).

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	tri_histogram : matplotlib figure object
		Histogram figure (only if input parameter 'plot' is True).
	tri_index : float
		Triangular index.

	Raises
	------
	TypeError
		If no input data for 'rpeaks' or 'nni' provided.

	Notes
	-----
	..	Default bin size set to recommended bin size of 1/128 (with 128Hz being the minimum recommended sampling
		frequency) as recommended by the HRV guidelines.
	..	'show' has only effect if 'plot' is also True.
	.. 	'legend' has only effect if 'plot' is also True.
	..	'figsize' has only effect if 'plot' is also True.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # If histogram should be plotted
    if plot:
        # Get histogram values
        fig, ax, D, bins = _get_histogram(nn,
                                          figsize=figsize,
                                          binsize=binsize,
                                          legend=legend,
                                          plot=plot)

        # Compute Triangular index: number of nn intervals / maximum value of the distribution
        tri_index = nn.size / D.max()

        # Add legend
        if legend:
            h = mpl.patches.Patch(facecolor='skyblue')
            x = mpl.patches.Patch(facecolor='g', alpha=0.0)
            dx = mpl.patches.Patch(facecolor='g', alpha=0.0)
            tri = mpl.patches.Patch(facecolor='white', alpha=0.0)
            ax.legend([h, x, dx, tri], [
                'Histogram D(NNI)',
                'D(X): %i' % D.max(),
                'X: %.3f' % bins[np.argmax(D)],
                'TriIndex: %.3f' % tri_index
            ],
                      loc=0)

        # Show plot
        if show:
            plt.show()

        # Output
        args = (
            fig,
            tri_index,
        )
        names = (
            'tri_histogram',
            'tri_index',
        )

    # If histogram should not be plotted
    else:
        D, bins = _get_histogram(nn,
                                 figsize=figsize,
                                 binsize=binsize,
                                 legend=legend,
                                 plot=plot)

        # Compute Triangular index: number of nn intervals / maximum value of the distribution
        tri_index = nn.size / D.max()

        # Output
        args = (tri_index, )
        names = ('tri_index', )

    return utils.ReturnTuple(args, names)
コード例 #29
0
ファイル: time_domain.py プロジェクト: udemirezen/pyhrv
def tinn(nni=None,
         rpeaks=None,
         binsize=7.8125,
         plot=True,
         show=True,
         figsize=None,
         legend=True):
    """Computes TINN based on the NN intervals histogram.

	References:	[Electrophysiology1996]
	Docs:		https://pyhrv.readthedocs.io/en/latest/_pages/api/time.html#tinn-tinn

	Parameters
	----------
	nni : array
		NN intervals in [ms] or [s].
	rpeaks : array
		R-peak times in [ms] or [s].
	binsize : int, float
		Bin size of the histogram bins (default: 7.8125ms).
	plot : bool
		If True, creates histogram plot using matplotlib, else uses numpy (data only, no plot).
	show : bool, optional
		If true, shows histogram (default: True).
	figsize : array, optional
		Matplotlib figure size (width, height) (default: (6, 6)).
	legend : bool, optional
		If True, adds legend to the histogram (default: True).

	Returns (biosppy.utils.ReturnTuple Object)
	------------------------------------------
	[key : format]
		Description.
	tinn_histogram : matplotlib figure object
		Histogram figure (only if input parameter 'plot' is True).
	tinn_n : float
		N value of the TINN computation.
	tinn_m : float
		M value of the TINN computation.
	tinn : float
		TINN value.

	Raises
	------
	TypeError (via 'check_input()')
		If no input data for 'rpeaks' or 'nni' provided.

	Notes
	-----
	..	Default bin size set to recommended bin size of 1/128 (with 128Hz being the minimum recommended sampling
		frequency) as recommended by the HRV guidelines.
	..	'show' has only effect if 'plot' is also True.
	.. 	'legend' has only effect if 'plot' is also True.
	..	'figsize' has only effect if 'plot' is also True.
	.. 	If both 'nni' and 'rpeaks' are provided, 'rpeaks' will be chosen over the 'nni' and the 'nni' data will be computed
		from the 'rpeaks'.

	"""
    # Check input
    nn = tools.check_input(nni, rpeaks)

    # Get Histogram data (with or without histogram plot figure)
    if plot:
        fig, ax, D, bins = _get_histogram(nn,
                                          figsize=figsize,
                                          binsize=binsize,
                                          legend=legend,
                                          plot=plot)
    else:
        D, bins = _get_histogram(nn,
                                 figsize=figsize,
                                 binsize=binsize,
                                 legend=legend,
                                 plot=plot)

    # Use only all bins except the last one to avoid indexing error with 'D' (otherwise bins.size = D.size + 1)
    # bins = np.asarray(bins[:-1])

    # Get bins of the triangular's N side (left side of the bin with the highest value of the distribution)
    n_bins = [bin for bin in bins if bin < bins[np.argmax(D)]]

    # Get bins of the triangular's M side (right side of the bin with the highest value of the distribution)
    m_bins = [bin for bin in bins if bin > bins[np.argmax(D)]]

    # Set a maximum error
    min_error = 2**14
    N = 0
    M = 0

    # Compute triangle and error for each possible N value within the bins
    for n in n_bins:
        # Compute triangle and error for each possible M value within the bins
        for m in m_bins:
            # Get bin indices and time array that are valid for q(t) (i.e. q(t)!=0 for N < t < M)
            qi = np.zeros(bins.size)
            for i, bin in enumerate(bins):
                qi[i] = (True if n <= bin <= m else False)
            t = bins[[i for i, q in enumerate(qi) if q]]

            # Compute linear function that describes the N side of the triangle (q(N) = 0 to q(X) = max(D(X)))
            qn = interp1d([t[0], bins[np.argmax(D)]], [0, np.max(D)],
                          'linear',
                          bounds_error=False)
            qn = qn(bins)

            # Compute linear function that describes the M side of the triangle (q(X) = max(D(X)) to q(M) = 0)
            qm = interp1d([bins[np.argmax(D)], t[-1]], [np.max(D), 0],
                          'linear',
                          bounds_error=False)
            qm = qm(bins)

            # Join the linear functions of both sides to single array
            q = np.zeros(len(bins))
            for i, val in enumerate(bins):
                if str(qn[i]) != 'nan':
                    q[i] = qn[i]
                elif str(qm[i]) != 'nan':
                    q[i] = qm[i]
                else:
                    q[i] = 0

            # Compute squared error
            error = np.sum([(D[i] - q[i])**2 for i, _ in enumerate(bins)])

            # Save N and M value if error is < smaller than before
            if error < min_error:
                N, M, min_error = n, m, error
                qf = q

    # Compute TINN
    tinn = M - N

    # If plot figure required, add interpolated triangle and other specified plot characteristics
    if plot:
        # Add triangle to the plot
        ax.plot([N, bins[np.argmax(D)]], [0, D.max()], 'r--', linewidth=0.8)
        ax.plot([bins[np.argmax(D)], M], [D.max(), 0], 'r--', linewidth=0.8)

        # Add legend
        if legend:
            h = mpl.patches.Patch(facecolor='skyblue')
            tri = mpl.lines.Line2D([0, 0], [0, 0],
                                   linestyle='--',
                                   linewidth=0.8,
                                   color='r')
            x = mpl.patches.Patch(facecolor='g', alpha=0.0)
            dx = mpl.patches.Patch(facecolor='g', alpha=0.0)
            n = mpl.patches.Patch(facecolor='white', alpha=0.0)
            m = mpl.patches.Patch(facecolor='white', alpha=0.0)
            tinn_ = mpl.patches.Patch(facecolor='white', alpha=0.0)
            ax.legend([h, tri, x, dx, n, m, tinn_], [
                'Histogram D(NNI)', 'Triangular Interpol.',
                'D(X): %i' % D.max(),
                'X: %.3f$ms$' % bins[np.argmax(D)],
                'N: %.3f$ms$' % N,
                'M: %.3fms' % M,
                'TINN: %.3fms' % tinn
            ],
                      loc=0)

        # Show plot
        if show:
            plt.show()

        # Output
        args = (
            fig,
            N,
            M,
            tinn,
        )
        names = (
            'tinn_histogram',
            'tinn_n',
            'tinn_m',
            'tinn',
        )
    else:
        # Output
        args = (
            N,
            M,
            tinn,
        )
        names = (
            'tinn_n',
            'tinn_m',
            'tinn',
        )

    return utils.ReturnTuple(args, names)
コード例 #30
0
def welch_psd(nni=None,
			  rpeaks=None,
			  fbands=None,
			  nfft=2**12,
			  detrend=True,
			  window='hamming',
			  show=True,
			  show_param=True,
			  legend=True,
			  mode='normal'):
	"""Computes a Power Spectral Density (PSD) estimation from the NNI series using the Welch’s method
	and computes all frequency domain parameters from this PSD according to the specified frequency bands.

	References: [Electrophysiology1996], [Umberto2017], [Welch2017]
	Docs:		https://pyhrv.readthedocs.io/en/latest kwa/_pages/api/frequency.html#welch-s-method-welch-psd

	Parameters
	----------
	nni : array
		NN-Intervals in [ms] or [s]
	rpeaks : array
		R-peak locations in [ms] or [s]
	fbands : dict, optional
		Dictionary with frequency bands (2-element tuples or list)
		Value format:	(lower_freq_band_boundary, upper_freq_band_boundary)
		Keys:	'ulf'	Ultra low frequency		(default: none) optional
				'vlf'	Very low frequency		(default: (0.000Hz, 0.04Hz))
				'lf'	Low frequency			(default: (0.04Hz - 0.15Hz))
				'hf'	High frequency			(default: (0.15Hz - 0.4Hz))
	nfft : int, optional
		Number of points computed for the FFT result (default: 2**12)
	detrend : bool optional
		If True, detrend NNI series by subtracting the mean NNI (default: True)
	window : scipy window function, optional
		Window function used for PSD estimation (default: 'hamming')
	show : bool, optional
		If true, show PSD plot (default: True)
	show_param : bool, optional
		If true, list all computed PSD parameters next to the plot (default: True)
	legend : bool, optional
		If true, add a legend with frequency bands to the plot (default: True)

	Returns
	-------
	results : biosppy.utils.ReturnTuple object
		All results of the Welch's method's PSD estimation (see list and keys below)

	Returned Parameters & Keys
	--------------------------
	..	Peak frequencies of all frequency bands in [Hz] (key: 'fft_peak')
	..	Absolute powers of all frequency bands in [ms^2][(key: 'fft_abs')
	..	Relative powers of all frequency bands [%] (key: 'fft_rel')
	..	Logarithmic powers of all frequency bands [-] (key: 'fft_log')
	..	Normalized powers of all frequency bands [-] (key: 'fft_norms')
	..	LF/HF ratio [-] (key: 'fft_ratio')
	..	Total power over all frequency bands in [ms^2] (key: 'fft_total')
	..	Interpolation method used for NNI interpolation (key: 'fft_interpolation')
	..	Resampling frequency used for NNI interpolation (key: 'fft_resampling_frequency')
	..	Spectral window used for PSD estimation of the Welch's method (key: 'fft_spectral_window)'

	Notes
	-----
	..	The returned BioSPPy ReturnTuple object contains all frequency band parameters in parameter specific tuples
		of length 4 when using the ULF frequency band or of length 3 when NOT using the ULF frequency band.
		The structures of those tuples are shown in this example below (fft_results = ReturnTuple object returned by
		this function):

			Using ULF, VLF, LF and HF frequency bands:
				fft_results['fft_peak'] = (ulf_peak, vlf_peak, lf_peak, hf_peak)

			Using VLF, LF and HF frequency bands:
				fft_results['fft_peak'] = (vlf_peak, lf_peak, hf_peak)

	..	If 'show_param' is true, the parameters (incl. frequency band limits) will be listed next to the graph and no
		legend with frequency band limits will be added to the plot graph itself, i.e. the effect of 'show_param'
		will be used over the 'legend' effect.
	..	Only one type of input data is required.
	.. 	If both 'nni' and 'rpeaks' are provided, 'rpeaks' will be chosen over the 'nni' and the 'nni' data will be computed
		from the 'rpeaks'.
	..	NN and R-peak series provided in [s] format will be converted to [ms] format.

	"""
	# Check input values
	nn = tools.check_input(nni, rpeaks)

	# Verify or set default frequency bands
	fbands = _check_freq_bands(fbands)

	# Resampling (with 4Hz) and interpolate
	# Because RRi are unevenly spaced we must interpolate it for accurate PSD estimation.
	fs = 4
	t = np.cumsum(nn)
	t -= t[0]
	f_interpol = sp.interpolate.interp1d(t, nn, 'cubic')
	t_interpol = np.arange(t[0], t[-1], 1000./fs)
	nn_interpol = f_interpol(t_interpol)

	# Subtract mean value from each sample for surpression of DC-offsets
	if detrend:
		nn_interpol = nn_interpol - np.mean(nn_interpol)

	# Adapt 'nperseg' according to the total duration of the NNI series (5min threshold = 300000ms)
	if t.max() < 300000:
		nperseg = nfft
	else:
		nperseg = 300

	# Compute power spectral density estimation (where the magic happens)
	frequencies, powers = welch(
		x=nn_interpol,
		fs=fs,
		window=window,
		nperseg=nperseg,
		nfft=nfft,
		scaling='density'
	)

	# Metadata
	args = (nfft, window, fs, 'cubic')
	names = ('fft_nfft', 'fft_window', 'fft_resampling_frequency', 'fft_interpolation',)
	meta = utils.ReturnTuple(args, names)

	if mode not in ['normal', 'dev', 'devplot']:
		warnings.warn("Unknown mode '%s'. Will proceed with 'normal' mode." % mode, stacklevel=2)
		mode = 'normal'

	# Normal Mode:
	# Returns frequency parameters, PSD plot figure and no frequency & power series/arrays
	if mode == 'normal':
		# Compute frequency parameters
		params, freq_i = _compute_parameters('fft', frequencies, powers, fbands)

		# Plot PSD
		figure = _plot_psd('fft', frequencies, powers, freq_i, params, show, show_param, legend)
		figure = utils.ReturnTuple((figure, ), ('fft_plot', ))

		# Output
		return tools.join_tuples(params, figure, meta)

	# Dev Mode:
	# Returns frequency parameters and frequency & power series/array; does not create a plot figure nor plot the data
	elif mode == 'dev':
		# Compute frequency parameters
		params, _ = _compute_parameters('fft', frequencies, powers, fbands)

		# Output
		return tools.join_tuples(params, meta), frequencies, (powers / 10 ** 6)

	# Devplot Mode:
	# Returns frequency parameters, PSD plot figure, and frequency & power series/arrays
	elif mode == 'devplot':
		# Compute frequency parameters
		params, freq_i = _compute_parameters('fft', frequencies, powers, fbands)

		# Plot PSD
		figure = _plot_psd('fft', frequencies, powers, freq_i, params, show, show_param, legend)
		figure = utils.ReturnTuple((figure, ), ('fft_plot', ))

		# Output
		return tools.join_tuples(params, figure, meta), frequencies, (powers / 10 ** 6)