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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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', ))
def welch_psd(self, nni=None, rpeaks=None, fbands=None, nfft=2**12, detrend=True, window='hamming', show=True, show_param=True, legend=True, mode='normal'): # Check input values nn = tools.check_input(nni, rpeaks) # Verify or set default frequency bands fbands = self._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 = self._compute_parameters('fft', frequencies, powers, fbands) # Plot PSD figure = self._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, _ = self._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 = self._compute_parameters('fft', frequencies, powers, fbands) # Plot PSD figure = self._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)
def frequency_domain(self, nni=None, rpeaks=None, signal=None, sampling_rate=1000., fbands=None, show=False, show_param=True, legend=True, kwargs_welch=None, kwargs_lomb=None, kwargs_ar=None): # Check input if signal is not None: rpeaks = biosppy.ecg.ecg(signal=signal, sampling_rate=sampling_rate, show=False)[2] elif nni is None and rpeaks is None: raise TypeError( 'No input data provided. Please specify input data.') # Get NNI series nn = tools.check_input(nni, rpeaks) # Check for kwargs for the 'welch_psd' function and compute the PSD if kwargs_welch is not None: if type(kwargs_welch) is not dict: raise TypeError( "Expected <type 'dict'>, got %s: 'kwargs_welch' must be a dictionary containing " "parameters (keys) and values for the 'welch_psd' function." % type(kwargs_welch)) # Supported kwargs available_kwargs = [ 'fbands', 'detrend', 'show', 'show_param', 'legend', 'window', 'nfft' ] # Unwrwap kwargs dictionary for Welch specific parameters detrend = kwargs_welch[ 'detrend'] if 'detrend' in kwargs_welch.keys() else True window = kwargs_welch['window'] if 'window' in kwargs_welch.keys( ) else 'hamming' nfft = kwargs_welch['nfft'] if 'nfft' in kwargs_welch.keys( ) else 2**12 unsupported_kwargs = [] for args in kwargs_welch.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 'welch_psd': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Welch's PSD with custom parameter settings welch_results = self.welch_psd(nn, fbands=fbands, detrend=detrend, show=False, show_param=show_param, legend=legend, nfft=nfft, window=window) else: # Compute Welch's PSD with default values welch_results = self.welch_psd(nn, show=False, fbands=fbands, legend=legend, show_param=show_param) # Check for kwargs for the 'welch_psd' function and compute the PSD if kwargs_lomb is not None: if type(kwargs_lomb) is not dict: raise TypeError( "Expected <type 'dict'>, got %s: 'kwargs_lomb' must be a dictionary containing " "parameters (keys) and values for the 'kwargs_lomb' function." % type(kwargs_lomb)) # Supported kwargs available_kwargs = [ 'fbands', 'ma_size', 'show', 'show_param', 'legend', 'nfft', '' ] # Unwrwap kwargs dictionary nfft = kwargs_lomb['nfft'] if 'nfft' in kwargs_lomb.keys( ) else 2**8 ma_size = kwargs_lomb['ma_size'] if 'ma_size' in kwargs_lomb.keys( ) else None unsupported_kwargs = [] for args in kwargs_lomb.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 'lomb_psd': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Welch's PSD with custom parameter settings lomb_results = self.lomb_psd(nn, fbands=fbands, ma_size=ma_size, show=False, show_param=show_param, legend=legend, nfft=nfft) else: # Compute Welch's PSD with default values lomb_results = self.lomb_psd(nn, show=False, fbands=fbands, legend=legend, show_param=show_param) # Check for kwargs for the 'ar_psd' function and compute the PSD if kwargs_ar is not None: if type(kwargs_ar) is not dict: raise TypeError( "Expected <type 'dict'>, got %s: 'kwargs_ar' must be a dictionary containing " "parameters (keys) and values for the 'ar_psd' function." % type(kwargs_ar)) # Supported kwargs available_kwargs = [ 'fbands', 'show', 'order', 'show_param', 'legend', 'window', 'nfft' ] # Unwrwap kwargs dictionary for Welch specific parameters nfft = kwargs_ar['nfft'] if 'nfft' in kwargs_ar.keys() else 2**12 order = kwargs_ar['order'] if 'order' in kwargs_ar.keys() else 16 unsupported_kwargs = [] for args in kwargs_ar.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 'welch_psd': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Autoregressive PSD with custom parameter settings ar_results = self.ar_psd(nn, fbands=fbands, order=order, show=False, show_param=show_param, legend=legend, nfft=nfft) else: # Compute Autoregressive PSD with default values ar_results = self.ar_psd(nn, show=False, fbands=fbands, legend=legend, show_param=show_param) # If plots should be shown (show all plots at once) if show: plt.show() # Output return tools.join_tuples(welch_results, lomb_results, ar_results)
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
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
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)
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)
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)
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)
def ar_psd(self, nni=None, rpeaks=None, fbands=None, nfft=2**12, order=16, show=True, show_param=True, legend=True, mode='normal'): # Check input nn = tools.check_input(nni, rpeaks) # Verify or set default frequency bands fbands = self._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 = self._compute_parameters('ar', frequencies, powers, fbands) # Plot PSD figure = self._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, _ = self._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 = self._compute_parameters('ar', frequencies, powers, fbands) # Plot PSD figure = self._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)
def nonlinear(nni=None, rpeaks=None, signal=None, sampling_rate=1000., show=False, kwargs_poincare=None, kwargs_sampen=None, kwargs_dfa=None): """Computes all time domain parameters of the HRV time domain module and returns them in a ReturnTuple object. References: [Peng1995][Willson2002] 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). show : bool, optional If True, shows DFA plot. kwargs_poincare : dict, optional Dictionary containing the kwargs for the nonlinear 'poincare()' function: .. 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'). kwargs_dfa : dict, optional Dictionary containing the kwargs for the nonlinear 'dfa()' function: .. 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) kwargs_sampen : dict, optional Dictionary containing the kwargs for the nonlinear 'sample_entropy()' function: .. 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 ------------------- .. 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 ---------------- .. Poincaré plot (key: 'poincare_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 (signal, nn, or rpeaks). .. Input data will be prioritized in the following order: 1. signal, 2. nn, 3. rpeaks. .. NN and R-peak series provided in [s] format will be converted to [ms] format. .. Currently only calls the poincare() function. Raises ------ TypeError If no input data for 'nn', 'rpeaks', and 'signal' provided. """ # Check input if signal is not None: rpeaks = ecg(signal=signal, sampling_rate=sampling_rate, show=False)[2] elif nni is None and rpeaks is None: raise TypeError('No input data provided. Please specify input data.') # Get NNI series nn = tools.check_input(nni, rpeaks) # Unwrap kwargs_poincare dictionary & compute Poincaré if kwargs_poincare is not None: if type(kwargs_poincare) is not dict: raise TypeError( "Expected <type 'dict'>, got %s: 'kwargs_poincare' must be a dictionary containing " "parameters (keys) and values for the 'poincare()' function." % type(kwargs_poincare)) # Supported kwargs available_kwargs = ['ellipse', 'vectors', 'legend', 'marker'] # Unwrwap kwargs dictionaries ellipse = kwargs_poincare[ 'ellipse'] if 'ellipse' in kwargs_poincare.keys() else True vectors = kwargs_poincare[ 'vectors'] if 'vectors' in kwargs_poincare.keys() else True legend = kwargs_poincare['legend'] if 'legend' in kwargs_poincare.keys( ) else True marker = kwargs_poincare['marker'] if 'marker' in kwargs_poincare.keys( ) else 'o' unsupported_kwargs = [] for args in kwargs_poincare.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 'poincare()': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Poincaré plot with custom configuration p_results = poincare(nn, show=False, ellipse=ellipse, vectors=vectors, legend=legend, marker=marker) else: # Compute Poincaré plot with default values p_results = poincare(nn, show=False) # Unwrap kwargs_sampen dictionary & compute Sample Entropy if kwargs_sampen is not None: if type(kwargs_sampen) is not dict: raise TypeError( "Expected <type 'dict'>, got %s: 'kwargs_sampen' must be a dictionary containing " "parameters (keys) and values for the 'sample_entropy()' function." % type(kwargs_sampen)) # Supported kwargs available_kwargs = ['dim', 'tolerance'] # Unwrwap kwargs dictionaries dim = kwargs_sampen['dim'] if 'dim' in kwargs_sampen.keys() else 2 tolerance = kwargs_sampen[ 'tolerance'] if 'tolerance' in kwargs_sampen.keys() else None unsupported_kwargs = [] for args in kwargs_sampen.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 'sample_entropy()': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Poincaré plot with custom configuration s_results = sample_entropy(nn, dim=dim, tolerance=tolerance) else: # Compute Poincaré plot with default values s_results = sample_entropy(nn) # Unwrap kwargs_dfa dictionary & compute Poincaré if kwargs_dfa is not None: if type(kwargs_dfa) is not dict: raise TypeError( "Expected <type 'dict'>, got %s: 'kwargs_dfa' must be a dictionary containing " "parameters (keys) and values for the 'dfa()' function." % type(kwargs_dfa)) # Supported kwargs available_kwargs = ['short', 'legend', 'long'] # Unwrwap kwargs dictionaries short = kwargs_dfa['short'] if 'short' in kwargs_dfa.keys() else None long = kwargs_dfa['long'] if 'long' in kwargs_dfa.keys() else None legend = kwargs_dfa['legend'] if 'legend' in kwargs_dfa.keys( ) else True unsupported_kwargs = [] for args in kwargs_dfa.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 'dfa()': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Poincaré plot with custom configuration d_results = dfa(nn, show=False, short=short, long=long, legend=legend) else: # Compute Poincaré plot with default values d_results = dfa(nn, show=False) # Join Results results = tools.join_tuples(p_results, s_results, d_results) # Plot if show: plt.show() # Output return results
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)
def frequency_domain(nni=None, rpeaks=None, signal=None, sampling_rate=1000., fbands=None, show=False, show_param=True, legend=True, kwargs_welch=None, kwargs_lomb=None, kwargs_ar=None): """Computes PSDs (Welch, Lomb, and Autoregressive), the parameters of the frequency domain, and returns the results in a ReturnTuple object. References: [Electrophysiology1996], [Kuusela2014], [Kallas2012], [Boardman2002], [Lomb1976], [Scargle1982], [Laguna1995], [Umberto2017], [Welch2017], [Basak2014] Docs: https://pyhrv.readthedocs.io/en/latest/_pages/api/frequency.html#module-level-function-frequency-domain Parameters ---------- nni : array_like NN intervals in (ms) or (s) rpeaks : array_like R-peak times in (ms) or (s) signal : array_like ECG signal sampling_rate : int, float Sampling rate used for the ECG acquisition in (Hz) fbands : 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)) show : bool, optional If True, show PSD plots show_param : bool, optional If True, list all computed parameters next to the plots legend : bool, optional If True, adds legends to the PSD plots kwargs_welch : dict, optional Dictionary containing the 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 Dictionary containing the 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 Dictionary containing the 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) Returns ------- results : biosppy.utils.ReturnTuple object All results of the frequency domain PSD estimation methods (see list and keys below) Returned Parameters & Keys -------------------------- (below, X = one of the methods '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 in [-] (key: 'X_log') .. Normalized powers of all frequency bands in [-] (key: 'X_norms') .. LF/HF ratio in [-] (key: 'X_ratio') .. Total power over all frequency bands in [ms^2] (key: 'X_total') .. Number of PSD samples (key: 'X_nfft') .. FFT-specific: Interpolation method used for NNI interpolation (key: 'fft_interpolation') .. FFT-specific: Resampling frequency used for NNI interpolation (key: 'fft_resampling_frequency') .. FFT-specific: Window function used for PSD estimation of the Welch's method (key: 'fft_window) .. Lomb-specific: Moving average window size (key: 'lomb_ma') .. AR-specific: Model order (key: 'ar_order') .. AR-specific: Interpolation method used for NNI interpolation (key: 'ar_interpolation') .. AR-specific: Resampling frequency used for NNI interpolation (key: 'ar_resampling_frequency') 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 .. 'fbands' and 'show' will be selected for all methods. Individually adding specific 'fbands' to 'kwargs_welch' or 'kwargs_lomb' will have no effect .. Select the 'nfft' individually for each method using the kwargs dictionaries of the respective method(s) """ # Check input if signal is not None: rpeaks = biosppy.ecg.ecg(signal=signal, sampling_rate=sampling_rate, show=False)[2] elif nni is None and rpeaks is None: raise TypeError('No input data provided. Please specify input data.') # Get NNI series nn = tools.check_input(nni, rpeaks) # Check for kwargs for the 'welch_psd' function and compute the PSD if kwargs_welch is not None: if type(kwargs_welch) is not dict: raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_welch' must be a dictionary containing " "parameters (keys) and values for the 'welch_psd' function." % type(kwargs_welch)) # Supported kwargs available_kwargs = ['fbands', 'detrend', 'show', 'show_param', 'legend', 'window', 'nfft'] # Unwrwap kwargs dictionary for Welch specific parameters detrend = kwargs_welch['detrend'] if 'detrend' in kwargs_welch.keys() else True window = kwargs_welch['window'] if 'window' in kwargs_welch.keys() else 'hamming' nfft = kwargs_welch['nfft'] if 'nfft' in kwargs_welch.keys() else 2**12 unsupported_kwargs = [] for args in kwargs_welch.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 'welch_psd': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Welch's PSD with custom parameter settings welch_results = welch_psd(nn, fbands=fbands, detrend=detrend, show=False, show_param=show_param, legend=legend, nfft=nfft, window=window) else: # Compute Welch's PSD with default values welch_results = welch_psd(nn, show=False, fbands=fbands, legend=legend, show_param=show_param) # Check for kwargs for the 'welch_psd' function and compute the PSD if kwargs_lomb is not None: if type(kwargs_lomb) is not dict: raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_lomb' must be a dictionary containing " "parameters (keys) and values for the 'kwargs_lomb' function." % type(kwargs_lomb)) # Supported kwargs available_kwargs = ['fbands', 'ma_size', 'show', 'show_param', 'legend', 'nfft', ''] # Unwrwap kwargs dictionary nfft = kwargs_lomb['nfft'] if 'nfft' in kwargs_lomb.keys() else 2**8 ma_size = kwargs_lomb['ma_size'] if 'ma_size' in kwargs_lomb.keys() else None unsupported_kwargs = [] for args in kwargs_lomb.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 'lomb_psd': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Welch's PSD with custom parameter settings lomb_results = lomb_psd(nn, fbands=fbands, ma_size=ma_size, show=False, show_param=show_param, legend=legend, nfft=nfft) else: # Compute Welch's PSD with default values lomb_results = lomb_psd(nn, show=False, fbands=fbands, legend=legend, show_param=show_param) # Check for kwargs for the 'ar_psd' function and compute the PSD if kwargs_ar is not None: if type(kwargs_ar) is not dict: raise TypeError("Expected <type 'dict'>, got %s: 'kwargs_ar' must be a dictionary containing " "parameters (keys) and values for the 'ar_psd' function." % type(kwargs_ar)) # Supported kwargs available_kwargs = ['fbands', 'show', 'order', 'show_param', 'legend', 'window', 'nfft'] # Unwrwap kwargs dictionary for Welch specific parameters nfft = kwargs_ar['nfft'] if 'nfft' in kwargs_ar.keys() else 2**12 order = kwargs_ar['order'] if 'order' in kwargs_ar.keys() else 16 unsupported_kwargs = [] for args in kwargs_ar.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 'welch_psd': %s. These kwargs have no effect." % unsupported_kwargs, stacklevel=2) # Compute Autoregressive PSD with custom parameter settings ar_results = ar_psd(nn, fbands=fbands, order=order, show=False, show_param=show_param, legend=legend, nfft=nfft) else: # Compute Autoregressive PSD with default values ar_results = ar_psd(nn, show=False, fbands=fbands, legend=legend, show_param=show_param) # If plots should be shown (show all plots at once) if show: plt.show() # Output return tools.join_tuples(welch_results, lomb_results, ar_results)
def time_domain(nni=None, rpeaks=None, signal=None, sampling_rate=1000., threshold=None, plot=True, show=False, binsize=7.8125): """Computes all time domain parameters of the HRV time domain module and returns them in a ReturnTuple object. 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, optional Sampling rate used for the ECG acquisition in [Hz] (default: 1000.). 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). figsize : array, optional Matplotlib figure size for the histogram (width, height) (default: (6, 6)) - (geometrical params). binsize : int, float Bin size in [ms] of the histogram bins - (geometrical params). legend : bool If True, highlights D(X) marker to the plot to be added to the legends (default=True) - (geometrical params). Returns ------- results : biosppy.utils.ReturnTuple object All time domain results (see list and keys below) Returned Parameters ------------------- .. 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') .. NNI histogram (key: 'nni_histogram') 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). .. Only one type of input data is required (signal, nni, or rpeaks). .. Input data will be prioritized in the following order: 1. signal, 2. nni, 3. rpeaks. .. SDNN Index and SDANN: In some cases, the NN interval may start in a segment (or .. 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. Raises ------ TypeError If no input data for 'nni', 'rpeaks', and 'signal' provided. """ # Check input if signal is not None: rpeaks = ecg(signal=signal, sampling_rate=sampling_rate, show=False)[2] elif nni is None and rpeaks is None: raise TypeError('No input data provided. Please specify input data.') # Get NNI series nn = tools.check_input(nni, rpeaks) # Call time domain functions & wrap results in a single biosspy.utils.ReturnTuple object results = nni_parameters(nn) results = tools.join_tuples(results, hr_parameters(nn)) results = tools.join_tuples(results, nni_differences_parameters(nn)) results = tools.join_tuples(results, sdnn(nn)) results = tools.join_tuples(results, sdnn_index(nn)) results = tools.join_tuples(results, sdann(nn)) results = tools.join_tuples(results, rmssd(nn)) results = tools.join_tuples(results, sdsd(nn)) results = tools.join_tuples(results, nn50(nn)) results = tools.join_tuples(results, nn20(nn)) # Compute custom threshold if required if threshold is not None: results = tools.join_tuples(results, nnXX(nn, threshold=int(threshold))) # Compute geometrical parameters results = tools.join_tuples( results, geometrical_parameters(nn, plot=plot, show=show, binsize=binsize)) # Output return results
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)
def lomb_psd(self, nni=None, rpeaks=None, fbands=None, nfft=2**8, ma_size=None, show=True, show_param=True, legend=True, mode='normal'): # Check input nn = tools.check_input(nni, rpeaks) # Verify or set default frequency bands fbands = self._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 = self._compute_parameters('lomb', frequencies, powers, fbands) # Plot parameters figure = self._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, _ = self._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 = self._compute_parameters('lomb', frequencies, powers, fbands) # Plot parameters figure = self._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