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 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)
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 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)
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 _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)
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 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)
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 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)
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 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)
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 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)
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)
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 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())
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 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 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 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 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 _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
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 _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)
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 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)