├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── biosppy ├── __init__.py ├── __version__.py ├── biometrics.py ├── clustering.py ├── inter_plotting │ ├── __init__.py │ ├── acc.py │ └── ecg.py ├── metrics.py ├── plotting.py ├── signals │ ├── __init__.py │ ├── abp.py │ ├── acc.py │ ├── bvp.py │ ├── ecg.py │ ├── eda.py │ ├── eeg.py │ ├── emg.py │ ├── pcg.py │ ├── ppg.py │ ├── resp.py │ └── tools.py ├── stats.py ├── storage.py ├── synthesizers │ ├── __init__.py │ └── ecg.py ├── timing.py └── utils.py ├── docs ├── Makefile ├── biosppy.rst ├── biosppy.signals.rst ├── conf.py ├── favicon.ico ├── images │ ├── ECG_raw.png │ └── ECG_summary.png ├── index.rst ├── logo │ ├── favicon.png │ ├── favicon.svg │ ├── logo.png │ ├── logo.svg │ ├── logo_400.png │ ├── logo_inverted_no_tag.png │ ├── logo_inverted_no_tag.svg │ ├── logo_inverted_no_tag_400.png │ ├── make_ico.bat │ └── make_small.bat ├── make.bat ├── requirements.txt └── tutorial.rst ├── example.py ├── examples ├── acc.txt ├── bcg.txt ├── ecg.txt ├── eda.txt ├── eeg_ec.txt ├── eeg_eo.txt ├── emg.txt ├── emg_1.txt ├── pcg.txt ├── ppg.txt └── resp.txt ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | .project 59 | .pydevproject 60 | .settings/org.eclipse.core.resources.prefs 61 | 62 | # Test 63 | test.py 64 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | BioSPPy is written and maintained by Carlos Carreiras and 2 | various contributors: 3 | 4 | Development Lead 5 | ---------------- 6 | 7 | - Carlos Carreiras () 8 | 9 | Main Contributors 10 | ----------------- 11 | 12 | - Ana Priscila Alves () 13 | - André Lourenço () 14 | - Filipe Canento () 15 | - Hugo Silva () 16 | 17 | Scientific Supervision 18 | ---------------------- 19 | 20 | - Ana Fred 21 | 22 | Patches and Suggestions 23 | ----------------------- 24 | 25 | - Hayden Ball (PR/7) 26 | - Jason Li (PR/13) 27 | - Dominique Makowski () (PR/15, PR/24) 28 | - Margarida Reis (PR/17) 29 | - Michael Gschwandtner (PR/23) 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | BioSPPy Changelog 2 | ================= 3 | 4 | Here you can see the full list of changes between each BioSPPy release. 5 | 6 | Version 0.8.0 7 | ------------- 8 | 9 | Released on December 20th 2021 10 | 11 | - Added PCG module to signals. 12 | - Fixed some bugs. 13 | 14 | Version 0.7.3 15 | ------------- 16 | 17 | Released on June 29th 2021 18 | 19 | - Removed BCG from master until some issues are fixed. 20 | 21 | Version 0.7.2 22 | ------------- 23 | 24 | Released on May 14th 2021 25 | 26 | - Fixed BCG dependencies. 27 | 28 | Version 0.7.1 29 | ------------- 30 | 31 | Released on May 14th 2021 32 | 33 | - Included BCG module. 34 | 35 | Version 0.7.0 36 | ------------- 37 | 38 | Released on May 7th 2021 39 | 40 | - GitHub and PyPI versions synced. 41 | 42 | Version 0.6.1 43 | ------------- 44 | 45 | Released on August 20th 2018 46 | 47 | - Fixed source file encoding 48 | 49 | Version 0.6.0 50 | ------------- 51 | 52 | Released on August 20th 2018 53 | 54 | - Added reference for BVP onset detection algorithm (closes #36) 55 | - Updated readme file 56 | - New setup.py style 57 | - Added online filtering class in signals.tools 58 | - Added Pearson correlation and RMSE methods in signals.tools 59 | - Added method to compute Welch's power spectrum in signals.tools 60 | - Don't use detrended derivative in signals.eda.kbk_scr (closes #43) 61 | - Various minor changes 62 | 63 | Version 0.5.1 64 | ------------- 65 | 66 | Released on November 29th 2017 67 | 68 | - Fixed bug when correcting r-peaks (closes #35) 69 | - Fixed a bug in the generation of the classifier thresholds 70 | - Added citation information to readme file (closes #34) 71 | - Various minor changes 72 | 73 | Version 0.5.0 74 | ------------- 75 | 76 | Released on August 28th 2017 77 | 78 | - Added a simple timing module 79 | - Added methods to help with file manipulations 80 | - Added a logo :camera: 81 | - Added the Matthews Correlation Coefficient as another authentication metric. 82 | - Fixed an issue in the ECG Hamilton algorithm (closes #28) 83 | - Various bug fixes 84 | 85 | Version 0.4.0 86 | ------------- 87 | 88 | Released on May 2nd 2017 89 | 90 | - Fixed array indexing with floats (merges #23) 91 | - Allow user to modify SCRs rejection treshold (merges #24) 92 | - Fixed the Scikit-Learn cross-validation module deprecation (closes #18) 93 | - Addd methods to compute mean and meadian of a set of n-dimensional data points 94 | - Added methods to compute the matrix profile 95 | - Added new EMG onset detection algorithms (merges #17) 96 | - Added finite difference method for numerial derivatives 97 | - Fixed inconsistent decibel usage in plotting (closes #16) 98 | 99 | Version 0.3.0 100 | ------------- 101 | 102 | Released on December 30th 2016 103 | 104 | - Lazy loading (merges #15) 105 | - Python 3 compatibility (merges #13) 106 | - Fixed usage of clustering linkage parameters 107 | - Fixed a bug when using filtering without the forward-backward technique 108 | - Bug fixes (closes #4, #8) 109 | - Allow BVP parameters as inputs (merges #7) 110 | 111 | Version 0.2.2 112 | ------------- 113 | 114 | Released on April 20th 2016 115 | 116 | - Makes use of new bidict API (closes #3) 117 | - Updates package version in the requirements file 118 | - Fixes incorrect EDA filter parameters 119 | - Fixes heart rate smoothing (size parameter) 120 | 121 | Version 0.2.1 122 | ------------- 123 | 124 | Released on January 6th 2016 125 | 126 | - Fixes incorrect BVP filter parameters (closes #2) 127 | 128 | Version 0.2.0 129 | ------------- 130 | 131 | Released on October 1st 2015 132 | 133 | - Added the biometrics module, including k-NN and SVM classifiers 134 | - Added outlier detection methods to the clustering module 135 | - Added text-based data storage methods to the storage module 136 | - Changed docstring style to napoleon-numpy 137 | - Complete code style formatting 138 | - Initial draft of the tutorial 139 | - Bug fixes 140 | 141 | Version 0.1.2 142 | ------------- 143 | 144 | Released on August 29th 2015 145 | 146 | - Alpha release 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Instituto de Telecomunicações. See AUTHORS for more details. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the software as well 6 | as documentation, with or without modification, are permitted provided 7 | that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 22 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 23 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 25 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | DAMAGE. 33 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE AUTHORS.md CHANGES.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This repository is archived. The BioSPPy toolbox is now maintained at [scientisst/BioSPPy](https://github.com/scientisst/BioSPPy). 2 | 3 | # BioSPPy - Biosignal Processing in Python 4 | 5 | *A toolbox for biosignal processing written in Python.* 6 | 7 | [![Image](https://github.com/PIA-Group/BioSPPy/raw/master/docs/logo/logo_400.png "I know you're listening! - xkcd.com/525")](http://biosppy.readthedocs.org/) 8 | 9 | The toolbox bundles together various signal processing and pattern recognition 10 | methods geared towards the analysis of biosignals. 11 | 12 | Highlights: 13 | 14 | - Support for various biosignals: BVP, ECG, EDA, EEG, EMG, PCG, PPG, Respiration 15 | - Signal analysis primitives: filtering, frequency analysis 16 | - Clustering 17 | - Biometrics 18 | 19 | Documentation can be found at: 20 | 21 | ## Installation 22 | 23 | Installation can be easily done with `pip`: 24 | 25 | ```bash 26 | $ pip install biosppy 27 | ``` 28 | 29 | ## Simple Example 30 | 31 | The code below loads an ECG signal from the `examples` folder, filters it, 32 | performs R-peak detection, and computes the instantaneous heart rate. 33 | 34 | ```python 35 | from biosppy import storage 36 | from biosppy.signals import ecg 37 | 38 | # load raw ECG signal 39 | signal, mdata = storage.load_txt('./examples/ecg.txt') 40 | 41 | # process it and plot 42 | out = ecg.ecg(signal=signal, sampling_rate=1000., show=True) 43 | ``` 44 | 45 | This should produce a plot similar to the one below. 46 | 47 | [![Image](https://github.com/PIA-Group/BioSPPy/raw/master/docs/images/ECG_summary.png "ECG Summary Plot")]() 48 | 49 | ## Dependencies 50 | 51 | - bidict 52 | - h5py 53 | - matplotlib 54 | - numpy 55 | - scikit-learn 56 | - scipy 57 | - shortuuid 58 | - six 59 | - joblib 60 | 61 | ## Citing 62 | Please use the following if you need to cite BioSPPy: 63 | 64 | - Carreiras C, Alves AP, Lourenço A, Canento F, Silva H, Fred A, *et al.* 65 | **BioSPPy - Biosignal Processing in Python**, 2015-, 66 | https://github.com/PIA-Group/BioSPPy/ [Online; accessed ```--```]. 67 | 68 | ```latex 69 | @Misc{, 70 | author = {Carlos Carreiras and Ana Priscila Alves and Andr\'{e} Louren\c{c}o and Filipe Canento and Hugo Silva and Ana Fred and others}, 71 | title = {{BioSPPy}: Biosignal Processing in {Python}}, 72 | year = {2015--}, 73 | url = "https://github.com/PIA-Group/BioSPPy/", 74 | note = {[Online; accessed ]} 75 | } 76 | ``` 77 | 78 | ## License 79 | 80 | BioSPPy is released under the BSD 3-clause license. See LICENSE for more details. 81 | 82 | ## Disclaimer 83 | 84 | This program is distributed in the hope it will be useful and provided 85 | to you "as is", but WITHOUT ANY WARRANTY, without even the implied 86 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. This 87 | program is NOT intended for medical diagnosis. We expressly disclaim any 88 | liability whatsoever for any direct, indirect, consequential, incidental 89 | or special damages, including, without limitation, lost revenues, lost 90 | profits, losses resulting from business interruption or loss of data, 91 | regardless of the form of action or legal theory under which the 92 | liability may be asserted, even if advised of the possibility of such 93 | damages. 94 | -------------------------------------------------------------------------------- /biosppy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy 4 | ------- 5 | 6 | A toolbox for biosignal processing written in Python. 7 | 8 | :copyright: (c) 2015-2021 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # compat 13 | from __future__ import absolute_import, division, print_function 14 | 15 | # get version 16 | from .__version__ import __version__ 17 | 18 | # allow lazy loading 19 | from .signals import acc, abp, bvp, ppg, pcg, ecg, eda, eeg, emg, resp, tools 20 | from .synthesizers import ecg 21 | from .inter_plotting import ecg, acc 22 | -------------------------------------------------------------------------------- /biosppy/__version__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.version 4 | --------------- 5 | 6 | Version tracker. 7 | 8 | :copyright: (c) 2015-2021 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | VERSION = (0, 8, 0) 13 | __version__ = ".".join(map(str, VERSION)) 14 | -------------------------------------------------------------------------------- /biosppy/inter_plotting/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals 4 | --------------- 5 | 6 | This package provides methods to interactively display plots for the 7 | following physiological signals (biosignals): 8 | * Electrocardiogram (ECG) 9 | 10 | :copyright: (c) 2015-2021 by Instituto de Telecomunicacoes 11 | :license: BSD 3-clause, see LICENSE for more details. 12 | """ 13 | 14 | # compat 15 | from __future__ import absolute_import, division, print_function 16 | 17 | # allow lazy loading 18 | from . import ecg, acc 19 | -------------------------------------------------------------------------------- /biosppy/inter_plotting/acc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.inter_plotting.ecg 4 | ------------------- 5 | 6 | This module provides an interactive display option for the ACC plot. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | 11 | """ 12 | 13 | # Imports 14 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk 15 | from matplotlib.backend_bases import key_press_handler 16 | import matplotlib.pyplot as plt 17 | import numpy as np 18 | from tkinter import * 19 | import tkinter.font as tkFont 20 | import sys 21 | import os 22 | 23 | # Globals 24 | from biosppy import utils 25 | 26 | MAJOR_LW = 2.5 27 | MINOR_LW = 1.5 28 | MAX_ROWS = 10 29 | 30 | 31 | def plot_acc(ts=None, raw=None, vm=None, sm=None, spectrum=None, path=None): 32 | """Create a summary plot from the output of signals.acc.acc. 33 | 34 | Parameters 35 | ---------- 36 | ts : array 37 | Signal time axis reference (seconds). 38 | raw : array 39 | Raw ACC signal. 40 | vm : array 41 | Vector Magnitude feature of the signal. 42 | sm : array 43 | Signal Magnitude feature of the signal 44 | path : str, optional 45 | If provided, the plot will be saved to the specified file. 46 | show : bool, optional 47 | If True, show the plot immediately. 48 | 49 | """ 50 | 51 | raw_t = np.transpose(raw) 52 | acc_x, acc_y, acc_z = raw_t[0], raw_t[1], raw_t[2] 53 | 54 | root = Tk() 55 | root.resizable(False, False) # default 56 | fig, axs_1 = plt.subplots(3, 1) 57 | axs_1[0].plot(ts, acc_x, linewidth=MINOR_LW, label="Raw acc along X", color="C0") 58 | axs_1[1].plot(ts, acc_y, linewidth=MINOR_LW, label="Raw acc along Y", color="C1") 59 | axs_1[2].plot(ts, acc_z, linewidth=MINOR_LW, label="Raw acc along Z", color="C2") 60 | 61 | axs_1[0].set_ylabel("Amplitude ($m/s^2$)") 62 | axs_1[1].set_ylabel("Amplitude ($m/s^2$)") 63 | axs_1[2].set_ylabel("Amplitude ($m/s^2$)") 64 | axs_1[2].set_xlabel("Time (s)") 65 | 66 | fig.suptitle("Acceleration signals") 67 | 68 | global share_axes_check_box 69 | 70 | class feature_figure: 71 | def __init__(self, reset=False): 72 | self.figure, self.axes = plt.subplots(1, 1) 73 | if not reset: 74 | self.avail_plots = [] 75 | 76 | def on_xlims_change(self, event_ax): 77 | print("updated xlims: ", event_ax.get_xlim()) 78 | 79 | def set_labels(self, x_label, y_label): 80 | self.axes.set_xlabel(x_label) 81 | self.axes.set_ylabel(y_label) 82 | 83 | def hide_content(self): 84 | # setting every plot element to white 85 | self.axes.spines["bottom"].set_color("white") 86 | self.axes.spines["top"].set_color("white") 87 | self.axes.spines["right"].set_color("white") 88 | self.axes.spines["left"].set_color("white") 89 | self.axes.tick_params(axis="x", colors="white") 90 | self.axes.tick_params(axis="y", colors="white") 91 | 92 | def show_content(self): 93 | # setting every plot element to black 94 | self.axes.spines["bottom"].set_color("black") 95 | self.axes.spines["top"].set_color("black") 96 | self.axes.spines["right"].set_color("black") 97 | self.axes.spines["left"].set_color("black") 98 | self.axes.tick_params(axis="x", colors="black") 99 | self.axes.tick_params(axis="y", colors="black") 100 | self.axes.legend() 101 | 102 | def draw_in_canvas(self, root: Tk, grid_params=None): 103 | if grid_params is None: 104 | grid_params = { 105 | "row": 2, 106 | "column": 1, 107 | "columnspan": 4, 108 | "sticky": "w", 109 | "padx": 10, 110 | } 111 | # add an empty canvas for plotting 112 | self.canvas = FigureCanvasTkAgg(self.figure, master=root) 113 | self.canvas.get_tk_widget().grid(**grid_params) 114 | self.canvas.draw() 115 | # self.axes.callbacks.connect('xlim_changed', on_xlims_change) 116 | 117 | def dump_canvas(self, root): 118 | self.axes.clear() 119 | self.figure.clear() 120 | self.figure.canvas.draw_idle() 121 | self.canvas = FigureCanvasTkAgg(self.figure, master=root) 122 | self.canvas.get_tk_widget().destroy() 123 | 124 | def add_toolbar(self, root: Tk, grid_params=None): 125 | if grid_params is None: 126 | grid_params = {"row": 5, "column": 1, "columnspan": 2, "sticky": "w"} 127 | toolbarFramefeat = Frame(master=root) 128 | toolbarFramefeat.grid(**grid_params) 129 | 130 | toolbarfeat = NavigationToolbar2Tk(self.canvas, toolbarFramefeat) 131 | toolbarfeat.update() 132 | 133 | def add_plot(self, feature_name: str, xdata, ydata, linewidth, label, color): 134 | feature_exists = False 135 | for features in self.avail_plots: 136 | if features["feature_name"] == feature_name: 137 | feature_exists = True 138 | break 139 | if not feature_exists: 140 | self._add_plot(feature_name, xdata, ydata, linewidth, label, color) 141 | 142 | def _add_plot(self, feature_name: str, xdata, ydata, linewidth, label, color): 143 | 144 | plot_params = { 145 | "feature_name": feature_name, 146 | "x": xdata, 147 | "y": ydata, 148 | "linewidth": linewidth, 149 | "label": label, 150 | "color": color, 151 | } 152 | 153 | plot_data = dict(plot_params) 154 | self.avail_plots.append(plot_data) 155 | del plot_params["feature_name"] 156 | del plot_params["x"] 157 | del plot_params["y"] 158 | self.axes.plot(xdata, ydata, **plot_params) 159 | self.show_content() 160 | 161 | def remove_plot(self, feature_name: str): 162 | if self.avail_plots: 163 | removed_index = [ 164 | i 165 | for i, x in enumerate(self.avail_plots) 166 | if x["feature_name"] == feature_name 167 | ] 168 | if len(removed_index) == 1: 169 | self._remove_plot(removed_index) 170 | 171 | def _remove_plot(self, removed_index): 172 | del self.avail_plots[removed_index[0]] 173 | self.__init__(reset=True) 174 | 175 | for params in self.avail_plots: 176 | temp_params = dict(params) 177 | del temp_params["feature_name"] 178 | del temp_params["x"] 179 | del temp_params["y"] 180 | self.axes.plot(params["x"], params["y"], **temp_params) 181 | 182 | if self.avail_plots == []: 183 | self.hide_content() 184 | else: 185 | self.show_content() 186 | 187 | def get_axes(self): 188 | return self.axes 189 | 190 | def get_figure(self): 191 | return self.figure 192 | 193 | # save to file 194 | if path is not None: 195 | path = utils.normpath(path) 196 | root_, ext = os.path.splitext(path) 197 | ext = ext.lower() 198 | if ext not in ["png", "jpg"]: 199 | path = root_ + ".png" 200 | 201 | fig.savefig(path, dpi=200, bbox_inches="tight") 202 | 203 | # window title 204 | root.wm_title("BioSPPy: acceleration signal") 205 | 206 | root.columnconfigure(0, weight=4) 207 | root.columnconfigure(1, weight=1) 208 | root.columnconfigure(2, weight=1) 209 | root.columnconfigure(3, weight=1) 210 | root.columnconfigure(4, weight=1) 211 | 212 | helv = tkFont.Font(family="Helvetica", size=20) 213 | 214 | # checkbox 215 | show_features_var = IntVar() 216 | share_axes_var = IntVar() 217 | 218 | def show_features(): 219 | global feat_fig 220 | global toolbarfeat 221 | global share_axes_check_box 222 | if show_features_var.get() == 0: 223 | drop_features2.get_menu().config(state="disabled") 224 | domain_feat_btn.config(state="disabled") 225 | 226 | # remove canvas for plotting 227 | feat_fig.dump_canvas(root) 228 | 229 | if show_features_var.get() == 1: 230 | # enable option menu for feature selection 231 | drop_features2.get_menu().config(state="normal") 232 | domain_feat_btn.config(state="normal") 233 | # canvas_features.get_tk_widget().grid(row=2, column=1, columnspan=1, sticky='w', padx=10) 234 | 235 | # add an empty canvas for plotting 236 | feat_fig = feature_figure() 237 | share_axes_check_box = Checkbutton( 238 | root, 239 | text="Share axes", 240 | variable=share_axes_var, 241 | onvalue=1, 242 | offvalue=0, 243 | command=lambda feat_fig=feat_fig: share_axes(feat_fig.get_axes()), 244 | ) 245 | share_axes_check_box.config(font=helv) 246 | share_axes_check_box.grid(row=4, column=1, sticky=W) 247 | 248 | feat_fig.hide_content() 249 | feat_fig.draw_in_canvas(root) 250 | 251 | def share_axes(ax2): 252 | if share_axes_var.get() == 1: 253 | axs_1[0].get_shared_x_axes().join(axs_1[0], ax2) 254 | axs_1[1].get_shared_x_axes().join(axs_1[1], ax2) 255 | axs_1[2].get_shared_x_axes().join(axs_1[2], ax2) 256 | 257 | else: 258 | for ax in axs_1: 259 | ax.get_shared_x_axes().remove(ax2) 260 | ax2.get_shared_x_axes().remove(ax) 261 | ax.autoscale() 262 | canvas_raw.draw() 263 | 264 | check1 = Checkbutton( 265 | root, 266 | text="Show features", 267 | variable=show_features_var, 268 | onvalue=1, 269 | offvalue=0, 270 | command=show_features, 271 | ) 272 | check1.config(font=helv) 273 | check1.grid(row=0, column=0, sticky=W) 274 | 275 | # FEATURES to be chosen 276 | clicked_features = StringVar() 277 | clicked_features.set("features") 278 | 279 | def domain_func(): 280 | global share_axes_check_box 281 | 282 | if feat_domain_var.get() == 1: 283 | domain_feat_btn["text"] = "Domain: frequency" 284 | feat_domain_var.set(0) 285 | feat_fig.remove_plot("VM") 286 | feat_fig.remove_plot("SM") 287 | feat_fig.draw_in_canvas(root) 288 | drop_features2.reset() 289 | drop_features2.reset_fields(["Spectra"]) 290 | share_axes_check_box.config(state="disabled") 291 | share_axes_var.set(0) 292 | 293 | else: 294 | domain_feat_btn["text"] = "Domain: time" 295 | feat_domain_var.set(1) 296 | feat_fig.remove_plot("SPECTRA X") 297 | feat_fig.remove_plot("SPECTRA Y") 298 | feat_fig.remove_plot("SPECTRA Z") 299 | feat_fig.draw_in_canvas(root) 300 | drop_features2.reset() 301 | drop_features2.reset_fields(["VM", "SM"]) 302 | share_axes_check_box.config(state="normal") 303 | 304 | feat_domain_var = IntVar() 305 | feat_domain_var.set(1) 306 | 307 | class feat_menu: 308 | def __init__(self, fieldnames: list, entry_name="Select Features", font=helv): 309 | self.feat_menu = Menubutton(root, text=entry_name, relief="raised") 310 | self.feat_menu.grid(row=0, column=2, sticky=W) 311 | self.feat_menu.menu = Menu(self.feat_menu, tearoff=0) 312 | self.feat_menu["menu"] = self.feat_menu.menu 313 | self.feat_menu["font"] = font 314 | self.font = font 315 | self.feat_activation = {} 316 | 317 | # setting up disabled fields 318 | for field in fieldnames: 319 | self.feat_activation[field] = False 320 | 321 | for field in fieldnames: 322 | self.feat_menu.menu.add_command( 323 | label=field, 324 | font=helv, 325 | command=lambda field=field: self.update_field(field), 326 | foreground="gray", 327 | ) 328 | self.fieldnames = fieldnames 329 | 330 | self.feat_menu.update() 331 | 332 | def reset(self, entry_name="Select Features"): 333 | self.feat_menu = Menubutton(root, text=entry_name, relief="raised") 334 | self.feat_menu.grid(row=0, column=2, sticky=W) 335 | self.feat_menu.menu = Menu(self.feat_menu, tearoff=0) 336 | self.feat_menu["menu"] = self.feat_menu.menu 337 | self.feat_menu["font"] = self.font 338 | self.feat_menu.update() 339 | 340 | def update_field(self, field): 341 | 342 | self.feat_activation[field] = not self.feat_activation[field] 343 | self.feat_menu.configure(text=field) # Set menu text to the selected event 344 | 345 | self.reset() 346 | 347 | for field_ in self.fieldnames: 348 | if self.feat_activation[field_]: 349 | self.feat_menu.menu.add_command( 350 | label=field_, 351 | font=helv, 352 | command=lambda field=field_: self.update_field(field), 353 | ) 354 | else: 355 | self.feat_menu.menu.add_command( 356 | label=field_, 357 | font=helv, 358 | command=lambda field=field_: self.update_field(field), 359 | foreground="gray", 360 | ) 361 | 362 | if field == "SM": 363 | if not self.feat_activation[field]: 364 | feat_fig.remove_plot("SM") 365 | if any(self.feat_activation.values()): 366 | feat_fig.set_labels("Time (s)", "Amplitude ($m/s^2$)") 367 | 368 | else: 369 | feat_fig.add_plot( 370 | "SM", 371 | ts, 372 | sm, 373 | linewidth=MINOR_LW, 374 | label="Signal Magnitude feature", 375 | color="C4", 376 | ) 377 | feat_fig.set_labels("Time (s)", "Amplitude ($m/s^2$)") 378 | 379 | feat_fig.draw_in_canvas(root) 380 | feat_fig.add_toolbar(root) 381 | 382 | elif field == "VM": 383 | if not self.feat_activation[field]: 384 | feat_fig.remove_plot("VM") 385 | if any(self.feat_activation.values()): 386 | feat_fig.set_labels("Time (s)", "Amplitude ($m/s^2$)") 387 | else: 388 | feat_fig.add_plot( 389 | "VM", 390 | ts, 391 | vm, 392 | linewidth=MINOR_LW, 393 | label="Vector Magnitude feature", 394 | color="C3", 395 | ) 396 | feat_fig.set_labels("Time (s)", "Amplitude ($m/s^2$)") 397 | 398 | feat_fig.draw_in_canvas(root) 399 | feat_fig.add_toolbar(root) 400 | 401 | elif field == "Spectra": 402 | if not self.feat_activation[field]: 403 | feat_fig.remove_plot("SPECTRA X") 404 | feat_fig.remove_plot("SPECTRA Y") 405 | feat_fig.remove_plot("SPECTRA Z") 406 | 407 | else: 408 | 409 | feat_fig.add_plot( 410 | "SPECTRA X", 411 | spectrum["freq"]["x"], 412 | spectrum["abs_amp"]["x"], 413 | linewidth=MINOR_LW, 414 | label="Spectrum along X", 415 | color="C0", 416 | ) 417 | feat_fig.draw_in_canvas(root) 418 | 419 | feat_fig.add_plot( 420 | "SPECTRA Y", 421 | spectrum["freq"]["y"], 422 | spectrum["abs_amp"]["y"], 423 | linewidth=MINOR_LW, 424 | label="Spectrum along Y", 425 | color="C1", 426 | ) 427 | feat_fig.draw_in_canvas(root) 428 | 429 | feat_fig.add_plot( 430 | "SPECTRA Z", 431 | spectrum["freq"]["z"], 432 | spectrum["abs_amp"]["z"], 433 | linewidth=MINOR_LW, 434 | label="Spectrum along Z", 435 | color="C2", 436 | ) 437 | feat_fig.set_labels( 438 | "Frequency ($Hz$)", "Normalized Amplitude [a.u.]" 439 | ) 440 | 441 | feat_fig.draw_in_canvas(root) 442 | feat_fig.add_toolbar(root) 443 | 444 | self.feat_menu.config(state="normal") 445 | self.feat_menu.update() 446 | 447 | def reset_fields(self, fieldnames): 448 | self.feat_activation = {} 449 | 450 | # setting up disabled fields 451 | for field in fieldnames: 452 | self.feat_activation[field] = False 453 | 454 | for field in fieldnames: 455 | self.feat_menu.menu.add_command( 456 | label=field, 457 | font=helv, 458 | command=lambda field=field: self.update_field(field), 459 | foreground="gray", 460 | ) 461 | 462 | self.fieldnames = fieldnames 463 | 464 | self.feat_menu.update() 465 | 466 | def get_menu(self): 467 | return self.feat_menu 468 | 469 | domain_feat_btn = Button(root, text="Domain: time", command=domain_func) 470 | domain_feat_btn.config(font=helv, state="disabled") 471 | domain_feat_btn.grid(row=0, column=1, sticky=W, padx=10) 472 | 473 | temp_features = ["VM", "SM"] 474 | 475 | drop_features2 = feat_menu(temp_features, entry_name="Select Features", font=helv) 476 | drop_features2.get_menu().config(state="disabled") 477 | drop_features2.get_menu().update() 478 | 479 | canvas_raw = FigureCanvasTkAgg(fig, master=root) 480 | canvas_raw.get_tk_widget().grid(row=2, column=0, columnspan=1, sticky="w", padx=10) 481 | canvas_raw.draw() 482 | 483 | toolbarFrame = Frame(master=root) 484 | toolbarFrame.grid(row=5, column=0, columnspan=1, sticky=W) 485 | toolbar = NavigationToolbar2Tk(canvas_raw, toolbarFrame) 486 | toolbar.update() 487 | 488 | # Add key functionality 489 | def on_key(event): 490 | # print('You pressed {}'.format(event.key)) 491 | key_press_handler(event, canvas_raw, toolbar) 492 | 493 | canvas_raw.mpl_connect("key_press_event", on_key) 494 | 495 | # tkinter main loop 496 | mainloop() 497 | -------------------------------------------------------------------------------- /biosppy/inter_plotting/ecg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.inter_plotting.ecg 4 | ------------------- 5 | 6 | This module provides an interactive display option for the ECG plot. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | 11 | """ 12 | 13 | # Imports 14 | from matplotlib import gridspec 15 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk 16 | 17 | # from matplotlib.backends.backend_wx import * 18 | import matplotlib.pyplot as plt 19 | import numpy as np 20 | from tkinter import * 21 | import os 22 | from biosppy import utils 23 | 24 | MAJOR_LW = 2.5 25 | MINOR_LW = 1.5 26 | MAX_ROWS = 10 27 | 28 | 29 | def plot_ecg( 30 | ts=None, 31 | raw=None, 32 | filtered=None, 33 | rpeaks=None, 34 | templates_ts=None, 35 | templates=None, 36 | heart_rate_ts=None, 37 | heart_rate=None, 38 | path=None, 39 | show=True, 40 | ): 41 | """Create a summary plot from the output of signals.ecg.ecg. 42 | 43 | Parameters 44 | ---------- 45 | ts : array 46 | Signal time axis reference (seconds). 47 | raw : array 48 | Raw ECG signal. 49 | filtered : array 50 | Filtered ECG signal. 51 | rpeaks : array 52 | R-peak location indices. 53 | templates_ts : array 54 | Templates time axis reference (seconds). 55 | templates : array 56 | Extracted heartbeat templates. 57 | heart_rate_ts : array 58 | Heart rate time axis reference (seconds). 59 | heart_rate : array 60 | Instantaneous heart rate (bpm). 61 | path : str, optional 62 | If provided, the plot will be saved to the specified file. 63 | show : bool, optional 64 | If True, show the plot immediately. 65 | 66 | """ 67 | 68 | # creating a root widget 69 | root_tk = Tk() 70 | root_tk.resizable(False, False) # default 71 | 72 | fig_raw, axs_raw = plt.subplots(3, 1, sharex=True) 73 | fig_raw.suptitle("ECG Summary") 74 | 75 | # raw signal plot (1) 76 | axs_raw[0].plot(ts, raw, linewidth=MAJOR_LW, label="Raw", color="C0") 77 | axs_raw[0].set_ylabel("Amplitude") 78 | axs_raw[0].legend() 79 | axs_raw[0].grid() 80 | 81 | # filtered signal with R-Peaks (2) 82 | axs_raw[1].plot(ts, filtered, linewidth=MAJOR_LW, label="Filtered", color="C0") 83 | 84 | ymin = np.min(filtered) 85 | ymax = np.max(filtered) 86 | alpha = 0.1 * (ymax - ymin) 87 | ymax += alpha 88 | ymin -= alpha 89 | 90 | # adding the R-Peaks 91 | axs_raw[1].vlines( 92 | ts[rpeaks], ymin, ymax, color="m", linewidth=MINOR_LW, label="R-peaks" 93 | ) 94 | 95 | axs_raw[1].set_ylabel("Amplitude") 96 | axs_raw[1].legend(loc="upper right") 97 | axs_raw[1].grid() 98 | 99 | # heart rate (3) 100 | axs_raw[2].plot(heart_rate_ts, heart_rate, linewidth=MAJOR_LW, label="Heart Rate") 101 | axs_raw[2].set_xlabel("Time (s)") 102 | axs_raw[2].set_ylabel("Heart Rate (bpm)") 103 | axs_raw[2].legend() 104 | axs_raw[2].grid() 105 | 106 | canvas_raw = FigureCanvasTkAgg(fig_raw, master=root_tk) 107 | canvas_raw.get_tk_widget().grid( 108 | row=0, column=0, columnspan=1, rowspan=6, sticky="w" 109 | ) 110 | canvas_raw.draw() 111 | 112 | toolbarFrame = Frame(master=root_tk) 113 | toolbarFrame.grid(row=6, column=0, columnspan=1, sticky=W) 114 | toolbar = NavigationToolbar2Tk(canvas_raw, toolbarFrame) 115 | toolbar.update() 116 | 117 | fig = fig_raw 118 | 119 | fig_2 = plt.Figure() 120 | gs = gridspec.GridSpec(6, 1) 121 | 122 | axs_2 = fig_2.add_subplot(gs[:, 0]) 123 | 124 | axs_2.plot(templates_ts, templates.T, "m", linewidth=MINOR_LW, alpha=0.7) 125 | axs_2.set_xlabel("Time (s)") 126 | axs_2.set_ylabel("Amplitude") 127 | axs_2.set_title("Templates") 128 | axs_2.grid() 129 | 130 | grid_params = {"row": 0, "column": 1, "columnspan": 2, "rowspan": 6, "sticky": "w"} 131 | canvas_2 = FigureCanvasTkAgg(fig_2, master=root_tk) 132 | canvas_2.get_tk_widget().grid(**grid_params) 133 | canvas_2.draw() 134 | 135 | toolbarFrame_2 = Frame(master=root_tk) 136 | toolbarFrame_2.grid(row=6, column=1, columnspan=1, sticky=W) 137 | toolbar_2 = NavigationToolbar2Tk(canvas_2, toolbarFrame_2) 138 | toolbar_2.update() 139 | 140 | if show: 141 | # window title 142 | root_tk.wm_title("BioSPPy: ECG signal") 143 | 144 | # save to file 145 | if path is not None: 146 | path = utils.normpath(path) 147 | root, ext = os.path.splitext(path) 148 | ext = ext.lower() 149 | if ext not in ["png", "jpg"]: 150 | path_block_1 = "{}-summary{}".format(root, ".png") 151 | path_block_2 = "{}-templates{}".format(root, ".png") 152 | else: 153 | path_block_1 = "{}-summary{}".format(root, ext) 154 | path_block_2 = "{}-templates{}".format(root, ext) 155 | 156 | fig.savefig(path_block_1, dpi=200, bbox_inches="tight") 157 | fig_2.savefig(path_block_2, dpi=200, bbox_inches="tight") 158 | 159 | mainloop() 160 | 161 | else: 162 | # close 163 | plt.close(fig) 164 | -------------------------------------------------------------------------------- /biosppy/metrics.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.metrics 4 | --------------- 5 | 6 | This module provides pairwise distance computation methods. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # Imports 13 | # compat 14 | from __future__ import absolute_import, division, print_function 15 | import six 16 | 17 | # 3rd party 18 | import numpy as np 19 | import scipy.spatial.distance as ssd 20 | from scipy import linalg 21 | 22 | 23 | def pcosine(u, v): 24 | """Computes the Cosine distance (positive space) between 1-D arrays. 25 | 26 | The Cosine distance (positive space) between `u` and `v` is defined as 27 | 28 | .. math:: 29 | 30 | d(u, v) = 1 - abs \\left( \\frac{u \\cdot v}{||u||_2 ||v||_2} \\right) 31 | 32 | where :math:`u \\cdot v` is the dot product of :math:`u` and :math:`v`. 33 | 34 | Parameters 35 | ---------- 36 | u : array 37 | Input array. 38 | v : array 39 | Input array. 40 | 41 | Returns 42 | ------- 43 | cosine : float 44 | Cosine distance between `u` and `v`. 45 | 46 | """ 47 | 48 | # validate vectors like scipy does 49 | u = ssd._validate_vector(u) 50 | v = ssd._validate_vector(v) 51 | 52 | dist = 1. - np.abs(np.dot(u, v) / (linalg.norm(u) * linalg.norm(v))) 53 | 54 | return dist 55 | 56 | 57 | def pdist(X, metric='euclidean', p=2, w=None, V=None, VI=None): 58 | """Pairwise distances between observations in n-dimensional space. 59 | 60 | Wraps scipy.spatial.distance.pdist. 61 | 62 | Parameters 63 | ---------- 64 | X : array 65 | An m by n array of m original observations in an n-dimensional space. 66 | metric : str, function, optional 67 | The distance metric to use; the distance can be 'braycurtis', 68 | 'canberra', 'chebyshev', 'cityblock', 'correlation', 'cosine', 'dice', 69 | 'euclidean', 'hamming', 'jaccard', 'kulsinski', 'mahalanobis', 70 | 'matching', 'minkowski', 'pcosine', 'rogerstanimoto', 'russellrao', 71 | 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'yule'. 72 | p : float, optional 73 | The p-norm to apply (for Minkowski, weighted and unweighted). 74 | w : array, optional 75 | The weight vector (for weighted Minkowski). 76 | V : array, optional 77 | The variance vector (for standardized Euclidean). 78 | VI : array, optional 79 | The inverse of the covariance matrix (for Mahalanobis). 80 | 81 | Returns 82 | ------- 83 | Y : array 84 | Returns a condensed distance matrix Y. For each :math:`i` and 85 | :math:`j` (where :math:`i length: 182 | stop = length 183 | 184 | idx = [] 185 | 186 | while True: 187 | sq = np.copy(signal[start:stop]) 188 | sq -= sq.mean() 189 | # sq = sq[1:] 190 | ss = 25 * ssf[start:stop] 191 | sss = 100 * np.diff(ss) 192 | sss[sss < 0] = 0 193 | sss = sss - alpha * np.mean(sss) 194 | 195 | # find maxima 196 | pk, pv = st.find_extrema(signal=sss, mode="max") 197 | pk = pk[np.nonzero(pv > 0)] 198 | pk += wrange 199 | dpidx = pk 200 | 201 | # analyze between maxima of 2nd derivative of ss 202 | detected = False 203 | for i in range(1, len(dpidx) + 1): 204 | try: 205 | v, u = dpidx[i - 1], dpidx[i] 206 | except IndexError: 207 | v, u = dpidx[-1], -1 208 | 209 | s = sq[v:u] 210 | Mk, Mv = st.find_extrema(signal=s, mode="max") 211 | mk, mv = st.find_extrema(signal=s, mode="min") 212 | 213 | try: 214 | M = Mk[np.argmax(Mv)] 215 | m = mk[np.argmax(mv)] 216 | except ValueError: 217 | continue 218 | 219 | if (s[M] - s[m] > d1_th) and (m - M > d2_th): 220 | idx += [v + start] 221 | detected = True 222 | 223 | # next round continues from previous detected beat 224 | if detected: 225 | start = idx[-1] + wrange 226 | else: 227 | start += size 228 | 229 | # stop condition 230 | if start > length: 231 | break 232 | 233 | # update stop 234 | stop += size 235 | if stop > length: 236 | stop = length 237 | 238 | idx = np.array(idx, dtype="int") 239 | 240 | return utils.ReturnTuple((idx,), ("onsets",)) 241 | -------------------------------------------------------------------------------- /biosppy/signals/acc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals.acc 4 | ------------------- 5 | 6 | This module provides methods to process Acceleration (ACC) signals. 7 | Implemented code assumes ACC acquisition from a 3 orthogonal axis reference system. 8 | 9 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 10 | :license: BSD 3-clause, see LICENSE for more details. 11 | 12 | Authors 13 | ------- 14 | Afonso Ferreira 15 | Diogo Vieira 16 | 17 | """ 18 | 19 | # Imports 20 | from __future__ import absolute_import, division, print_function 21 | from six.moves import range 22 | 23 | # 3rd party 24 | import numpy as np 25 | 26 | # local 27 | from .. import plotting, utils 28 | from biosppy.inter_plotting import acc as inter_plotting 29 | 30 | 31 | def acc(signal=None, sampling_rate=100.0, path=None, show=True, interactive=True): 32 | """Process a raw ACC signal and extract relevant signal features using 33 | default parameters. 34 | 35 | Parameters 36 | ---------- 37 | signal : array 38 | Raw ACC signal. 39 | sampling_rate : int, float, optional 40 | Sampling frequency (Hz). 41 | path : str, optional 42 | If provided, the plot will be saved to the specified file. 43 | show : bool, optional 44 | If True, show a summary plot. 45 | interactive : bool, optional 46 | If True, shows an interactive plot. 47 | 48 | Returns 49 | ------- 50 | ts : array 51 | Signal time axis reference (seconds). 52 | signal : array 53 | Raw (unfiltered) ACC signal. 54 | vm : array 55 | Vector Magnitude feature of the signal. 56 | sm : array 57 | Signal Magnitude feature of the signal. 58 | freq_features : dict 59 | Positive Frequency domains (Hz) of the signal. 60 | amp_features : dict 61 | Normalized Absolute Amplitudes of the signal. 62 | 63 | """ 64 | 65 | # check inputs 66 | if signal is None: 67 | raise TypeError("Please specify an input signal.") 68 | 69 | # ensure numpy 70 | signal = np.array(signal) 71 | 72 | sampling_rate = float(sampling_rate) 73 | 74 | # extract features 75 | vm_features, sm_features = time_domain_feature_extractor(signal=signal) 76 | freq_features, abs_amp_features = frequency_domain_feature_extractor( 77 | signal=signal, sampling_rate=sampling_rate 78 | ) 79 | 80 | # get time vectors 81 | length = len(signal) 82 | T = (length - 1) / sampling_rate 83 | ts = np.linspace(0, T, length, endpoint=True) 84 | 85 | # plot 86 | if show: 87 | if interactive: 88 | inter_plotting.plot_acc( 89 | ts=ts, # plotting.plot_acc 90 | raw=signal, 91 | vm=vm_features, 92 | sm=sm_features, 93 | spectrum={"freq": freq_features, "abs_amp": abs_amp_features}, 94 | path=path, 95 | ) 96 | else: 97 | plotting.plot_acc( 98 | ts=ts, # plotting.plot_acc 99 | raw=signal, 100 | vm=vm_features, 101 | sm=sm_features, 102 | path=path, 103 | show=True, 104 | ) 105 | 106 | # output 107 | args = (ts, signal, vm_features, sm_features, freq_features, abs_amp_features) 108 | names = ("ts", "signal", "vm", "sm", "freq", "abs_amp") 109 | 110 | return utils.ReturnTuple(args, names) 111 | 112 | 113 | def time_domain_feature_extractor(signal=None): 114 | """Extracts the vector magnitude and signal magnitude features from an input ACC signal, given the signal itself. 115 | 116 | Parameters 117 | ---------- 118 | signal : array 119 | Input ACC signal. 120 | 121 | Returns 122 | ------- 123 | vm_features : array 124 | Extracted Vector Magnitude (VM) feature. 125 | sm_features : array 126 | Extracted Signal Magnitude (SM) feature. 127 | 128 | """ 129 | 130 | # check inputs 131 | if signal is None: 132 | raise TypeError("Please specify an input signal.") 133 | 134 | # get acceleration features 135 | vm_features = np.zeros(signal.shape[0]) 136 | sm_features = np.zeros(signal.shape[0]) 137 | 138 | for i in range(signal.shape[0]): 139 | vm_features[i] = np.linalg.norm( 140 | np.array([signal[i][0], signal[i][1], signal[i][2]]) 141 | ) 142 | sm_features[i] = (abs(signal[i][0]) + abs(signal[i][1]) + abs(signal[i][2])) / 3 143 | 144 | return utils.ReturnTuple((vm_features, sm_features), ("vm", "sm")) 145 | 146 | 147 | def frequency_domain_feature_extractor(signal=None, sampling_rate=100.0): 148 | """Extracts the FFT from each ACC sub-signal (x, y, z), given the signal itself. 149 | 150 | Parameters 151 | ---------- 152 | signal : array 153 | Input ACC signal. 154 | sampling_rate : int, float, optional 155 | Sampling frequency (Hz). 156 | 157 | Returns 158 | ------- 159 | freq_features : dict 160 | Dictionary of positive frequencies (Hz) for all sub-signals. 161 | amp_features : dict 162 | Dictionary of Normalized Absolute Amplitudes for all sub-signals. 163 | 164 | """ 165 | 166 | # check inputs 167 | if signal is None: 168 | raise TypeError("Please specify an input signal.") 169 | 170 | freq_features = {} 171 | amp_features = {} 172 | 173 | # get Normalized FFT for each sub-signal 174 | for ind, axis in zip(range(signal.shape[1]), ["x", "y", "z"]): 175 | sub_signal = signal[:, ind] 176 | 177 | n = len(sub_signal) 178 | k = np.arange(n) 179 | T = n / sampling_rate 180 | frq = k / T 181 | freq_features[axis] = frq[range(n // 2)] 182 | 183 | amp = np.fft.fft(sub_signal) / n 184 | amp_features[axis] = abs(amp[range(n // 2)]) 185 | 186 | return utils.ReturnTuple((freq_features, amp_features), ("freq", "abs_amp")) 187 | -------------------------------------------------------------------------------- /biosppy/signals/bvp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals.bvp 4 | ------------------- 5 | 6 | This module provides methods to process Blood Volume Pulse (BVP) signals. 7 | 8 | -------- DEPRECATED -------- 9 | PLEASE, USE THE PPG MODULE 10 | This module was left for compatibility 11 | ---------------------------- 12 | 13 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 14 | :license: BSD 3-clause, see LICENSE for more details. 15 | """ 16 | 17 | # Imports 18 | # compat 19 | from __future__ import absolute_import, division, print_function 20 | from six.moves import range 21 | 22 | # 3rd party 23 | import numpy as np 24 | 25 | # local 26 | from . import tools as st 27 | from . import ppg 28 | from .. import plotting, utils 29 | 30 | 31 | def bvp(signal=None, sampling_rate=1000., path=None, show=True): 32 | """Process a raw BVP signal and extract relevant signal features using 33 | default parameters. 34 | Parameters 35 | ---------- 36 | signal : array 37 | Raw BVP signal. 38 | sampling_rate : int, float, optional 39 | Sampling frequency (Hz). 40 | path : str, optional 41 | If provided, the plot will be saved to the specified file. 42 | show : bool, optional 43 | If True, show a summary plot. 44 | Returns 45 | ------- 46 | ts : array 47 | Signal time axis reference (seconds). 48 | filtered : array 49 | Filtered BVP signal. 50 | onsets : array 51 | Indices of BVP pulse onsets. 52 | heart_rate_ts : array 53 | Heart rate time axis reference (seconds). 54 | heart_rate : array 55 | Instantaneous heart rate (bpm). 56 | """ 57 | 58 | # check inputs 59 | if signal is None: 60 | raise TypeError("Please specify an input signal.") 61 | 62 | # ensure numpy 63 | signal = np.array(signal) 64 | 65 | sampling_rate = float(sampling_rate) 66 | 67 | # filter signal 68 | filtered, _, _ = st.filter_signal(signal=signal, 69 | ftype='butter', 70 | band='bandpass', 71 | order=4, 72 | frequency=[1, 8], 73 | sampling_rate=sampling_rate) 74 | 75 | # find onsets 76 | onsets,_ = ppg.find_onsets_elgendi2013(signal=filtered, sampling_rate=sampling_rate) 77 | 78 | # compute heart rate 79 | hr_idx, hr = st.get_heart_rate(beats=onsets, 80 | sampling_rate=sampling_rate, 81 | smooth=True, 82 | size=3) 83 | 84 | # get time vectors 85 | length = len(signal) 86 | T = (length - 1) / sampling_rate 87 | ts = np.linspace(0, T, length, endpoint=False) 88 | ts_hr = ts[hr_idx] 89 | 90 | # plot 91 | if show: 92 | plotting.plot_bvp(ts=ts, 93 | raw=signal, 94 | filtered=filtered, 95 | onsets=onsets, 96 | heart_rate_ts=ts_hr, 97 | heart_rate=hr, 98 | path=path, 99 | show=True) 100 | 101 | # output 102 | args = (ts, filtered, onsets, ts_hr, hr) 103 | names = ('ts', 'filtered', 'onsets', 'heart_rate_ts', 'heart_rate') 104 | 105 | return utils.ReturnTuple(args, names) 106 | 107 | 108 | -------------------------------------------------------------------------------- /biosppy/signals/eda.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals.eda 4 | ------------------- 5 | 6 | This module provides methods to process Electrodermal Activity (EDA) 7 | signals, also known as Galvanic Skin Response (GSR). 8 | 9 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 10 | :license: BSD 3-clause, see LICENSE for more details. 11 | """ 12 | 13 | # Imports 14 | # compat 15 | from __future__ import absolute_import, division, print_function 16 | from six.moves import range 17 | 18 | # 3rd party 19 | import numpy as np 20 | 21 | # local 22 | from . import tools as st 23 | from .. import plotting, utils 24 | 25 | 26 | def eda(signal=None, sampling_rate=1000.0, path=None, show=True, min_amplitude=0.1): 27 | """Process a raw EDA signal and extract relevant signal features using 28 | default parameters. 29 | 30 | Parameters 31 | ---------- 32 | signal : array 33 | Raw EDA signal. 34 | sampling_rate : int, float, optional 35 | Sampling frequency (Hz). 36 | path : str, optional 37 | If provided, the plot will be saved to the specified file. 38 | show : bool, optional 39 | If True, show a summary plot. 40 | min_amplitude : float, optional 41 | Minimum treshold by which to exclude SCRs. 42 | 43 | Returns 44 | ------- 45 | ts : array 46 | Signal time axis reference (seconds). 47 | filtered : array 48 | Filtered EDA signal. 49 | onsets : array 50 | Indices of SCR pulse onsets. 51 | peaks : array 52 | Indices of the SCR peaks. 53 | amplitudes : array 54 | SCR pulse amplitudes. 55 | 56 | """ 57 | 58 | # check inputs 59 | if signal is None: 60 | raise TypeError("Please specify an input signal.") 61 | 62 | # ensure numpy 63 | signal = np.array(signal) 64 | 65 | sampling_rate = float(sampling_rate) 66 | 67 | # filter signal 68 | aux, _, _ = st.filter_signal( 69 | signal=signal, 70 | ftype="butter", 71 | band="lowpass", 72 | order=4, 73 | frequency=5, 74 | sampling_rate=sampling_rate, 75 | ) 76 | 77 | # smooth 78 | sm_size = int(0.75 * sampling_rate) 79 | filtered, _ = st.smoother(signal=aux, kernel="boxzen", size=sm_size, mirror=True) 80 | 81 | # get SCR info 82 | onsets, peaks, amps = kbk_scr( 83 | signal=filtered, sampling_rate=sampling_rate, min_amplitude=min_amplitude 84 | ) 85 | 86 | # get time vectors 87 | length = len(signal) 88 | T = (length - 1) / sampling_rate 89 | ts = np.linspace(0, T, length, endpoint=True) 90 | 91 | # plot 92 | if show: 93 | plotting.plot_eda( 94 | ts=ts, 95 | raw=signal, 96 | filtered=filtered, 97 | onsets=onsets, 98 | peaks=peaks, 99 | amplitudes=amps, 100 | path=path, 101 | show=True, 102 | ) 103 | 104 | # output 105 | args = (ts, filtered, onsets, peaks, amps) 106 | names = ("ts", "filtered", "onsets", "peaks", "amplitudes") 107 | 108 | return utils.ReturnTuple(args, names) 109 | 110 | 111 | def basic_scr(signal=None, sampling_rate=1000.0): 112 | """Basic method to extract Skin Conductivity Responses (SCR) from an 113 | EDA signal. 114 | 115 | Follows the approach in [Gamb08]_. 116 | 117 | Parameters 118 | ---------- 119 | signal : array 120 | Input filterd EDA signal. 121 | sampling_rate : int, float, optional 122 | Sampling frequency (Hz). 123 | 124 | Returns 125 | ------- 126 | onsets : array 127 | Indices of the SCR onsets. 128 | peaks : array 129 | Indices of the SRC peaks. 130 | amplitudes : array 131 | SCR pulse amplitudes. 132 | 133 | References 134 | ---------- 135 | .. [Gamb08] Hugo Gamboa, "Multi-modal Behavioral Biometrics Based on HCI 136 | and Electrophysiology", PhD thesis, Instituto Superior T{\'e}cnico, 2008 137 | 138 | """ 139 | 140 | # check inputs 141 | if signal is None: 142 | raise TypeError("Please specify an input signal.") 143 | 144 | # find extrema 145 | pi, _ = st.find_extrema(signal=signal, mode="max") 146 | ni, _ = st.find_extrema(signal=signal, mode="min") 147 | 148 | # sanity check 149 | if len(pi) == 0 or len(ni) == 0: 150 | raise ValueError("Could not find SCR pulses.") 151 | 152 | # pair vectors 153 | if ni[0] > pi[0]: 154 | ni = ni[1:] 155 | if pi[-1] < ni[-1]: 156 | pi = pi[:-1] 157 | if len(pi) > len(ni): 158 | pi = pi[:-1] 159 | 160 | li = min(len(pi), len(ni)) 161 | i1 = pi[:li] 162 | i3 = ni[:li] 163 | 164 | # indices 165 | i0 = np.array((i1 + i3) / 2.0, dtype=int) 166 | if i0[0] < 0: 167 | i0[0] = 0 168 | 169 | # amplitude 170 | a = signal[i0] - signal[i3] 171 | 172 | # output 173 | args = (i3, i0, a) 174 | names = ("onsets", "peaks", "amplitudes") 175 | 176 | return utils.ReturnTuple(args, names) 177 | 178 | 179 | def kbk_scr(signal=None, sampling_rate=1000.0, min_amplitude=0.1): 180 | """KBK method to extract Skin Conductivity Responses (SCR) from an 181 | EDA signal. 182 | 183 | Follows the approach by Kim *et al.* [KiBK04]_. 184 | 185 | Parameters 186 | ---------- 187 | signal : array 188 | Input filterd EDA signal. 189 | sampling_rate : int, float, optional 190 | Sampling frequency (Hz). 191 | min_amplitude : float, optional 192 | Minimum treshold by which to exclude SCRs. 193 | 194 | Returns 195 | ------- 196 | onsets : array 197 | Indices of the SCR onsets. 198 | peaks : array 199 | Indices of the SRC peaks. 200 | amplitudes : array 201 | SCR pulse amplitudes. 202 | 203 | References 204 | ---------- 205 | .. [KiBK04] K.H. Kim, S.W. Bang, and S.R. Kim, "Emotion recognition 206 | system using short-term monitoring of physiological signals", 207 | Med. Biol. Eng. Comput., vol. 42, pp. 419-427, 2004 208 | 209 | """ 210 | 211 | # check inputs 212 | if signal is None: 213 | raise TypeError("Please specify an input signal.") 214 | 215 | # differentiation 216 | df = np.diff(signal) 217 | 218 | # smooth 219 | size = int(1.0 * sampling_rate) 220 | df, _ = st.smoother(signal=df, kernel="bartlett", size=size, mirror=True) 221 | 222 | # zero crosses 223 | (zeros,) = st.zero_cross(signal=df, detrend=False) 224 | if np.all(df[: zeros[0]] > 0): 225 | zeros = zeros[1:] 226 | if np.all(df[zeros[-1] :] > 0): 227 | zeros = zeros[:-1] 228 | 229 | scrs, amps, ZC, pks = [], [], [], [] 230 | for i in range(0, len(zeros) - 1, 2): 231 | scrs += [df[zeros[i] : zeros[i + 1]]] 232 | ZC += [zeros[i]] 233 | ZC += [zeros[i + 1]] 234 | pks += [zeros[i] + np.argmax(df[zeros[i] : zeros[i + 1]])] 235 | amps += [signal[pks[-1]] - signal[ZC[-2]]] 236 | 237 | # exclude SCRs with small amplitude 238 | thr = min_amplitude * np.max(amps) 239 | idx = np.where(amps > thr) 240 | 241 | scrs = np.array(scrs, dtype=np.object)[idx] 242 | amps = np.array(amps)[idx] 243 | ZC = np.array(ZC)[np.array(idx) * 2] 244 | pks = np.array(pks, dtype=int)[idx] 245 | 246 | onsets = ZC[0].astype(int) 247 | 248 | # output 249 | args = (onsets, pks, amps) 250 | names = ("onsets", "peaks", "amplitudes") 251 | 252 | return utils.ReturnTuple(args, names) 253 | -------------------------------------------------------------------------------- /biosppy/signals/eeg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals.eeg 4 | ------------------- 5 | 6 | This module provides methods to process Electroencephalographic (EEG) 7 | signals. 8 | 9 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 10 | :license: BSD 3-clause, see LICENSE for more details. 11 | """ 12 | 13 | # Imports 14 | # compat 15 | from __future__ import absolute_import, division, print_function 16 | from six.moves import range 17 | 18 | # 3rd party 19 | import numpy as np 20 | 21 | # local 22 | from . import tools as st 23 | from .. import plotting, utils 24 | 25 | 26 | def eeg(signal=None, sampling_rate=1000.0, labels=None, path=None, show=True): 27 | """Process raw EEG signals and extract relevant signal features using 28 | default parameters. 29 | 30 | 31 | Parameters 32 | ---------- 33 | signal : array 34 | Raw EEG signal matrix; each column is one EEG channel. 35 | sampling_rate : int, float, optional 36 | Sampling frequency (Hz). 37 | labels : list, optional 38 | Channel labels. 39 | path : str, optional 40 | If provided, the plot will be saved to the specified file. 41 | show : bool, optional 42 | If True, show a summary plot. 43 | 44 | Returns 45 | ------- 46 | ts : array 47 | Signal time axis reference (seconds). 48 | filtered : array 49 | Filtered EEG signal. 50 | features_ts : array 51 | Features time axis reference (seconds). 52 | theta : array 53 | Average power in the 4 to 8 Hz frequency band; each column is one EEG 54 | channel. 55 | alpha_low : array 56 | Average power in the 8 to 10 Hz frequency band; each column is one EEG 57 | channel. 58 | alpha_high : array 59 | Average power in the 10 to 13 Hz frequency band; each column is one EEG 60 | channel. 61 | beta : array 62 | Average power in the 13 to 25 Hz frequency band; each column is one EEG 63 | channel. 64 | gamma : array 65 | Average power in the 25 to 40 Hz frequency band; each column is one EEG 66 | channel. 67 | plf_pairs : list 68 | PLF pair indices. 69 | plf : array 70 | PLF matrix; each column is a channel pair. 71 | 72 | """ 73 | 74 | # check inputs 75 | if signal is None: 76 | raise TypeError("Please specify an input signal.") 77 | 78 | # ensure numpy 79 | signal = np.array(signal) 80 | signal = np.reshape(signal, (signal.shape[0], -1)) 81 | 82 | sampling_rate = float(sampling_rate) 83 | nch = signal.shape[1] 84 | 85 | if labels is None: 86 | labels = ["Ch. %d" % i for i in range(nch)] 87 | else: 88 | if len(labels) != nch: 89 | raise ValueError( 90 | "Number of channels mismatch between signal matrix and labels." 91 | ) 92 | 93 | # high pass filter 94 | b, a = st.get_filter( 95 | ftype="butter", 96 | band="highpass", 97 | order=8, 98 | frequency=4, 99 | sampling_rate=sampling_rate, 100 | ) 101 | 102 | aux, _ = st._filter_signal(b, a, signal=signal, check_phase=True, axis=0) 103 | 104 | # low pass filter 105 | b, a = st.get_filter( 106 | ftype="butter", 107 | band="lowpass", 108 | order=16, 109 | frequency=40, 110 | sampling_rate=sampling_rate, 111 | ) 112 | 113 | filtered, _ = st._filter_signal(b, a, signal=aux, check_phase=True, axis=0) 114 | 115 | # band power features 116 | out = get_power_features( 117 | signal=filtered, sampling_rate=sampling_rate, size=0.25, overlap=0.5 118 | ) 119 | ts_feat = out["ts"] 120 | theta = out["theta"] 121 | alpha_low = out["alpha_low"] 122 | alpha_high = out["alpha_high"] 123 | beta = out["beta"] 124 | gamma = out["gamma"] 125 | 126 | # If the input EEG is single channel do not extract plf 127 | # Initialises plf related vars for input and output requirement of plot_eeg function in case of nch <=1 128 | plf_pairs = [] 129 | plf = [] 130 | if nch > 1: 131 | # PLF features 132 | _, plf_pairs, plf = get_plf_features( 133 | signal=filtered, sampling_rate=sampling_rate, size=0.25, overlap=0.5 134 | ) 135 | 136 | # get time vectors 137 | length = len(signal) 138 | T = (length - 1) / sampling_rate 139 | ts = np.linspace(0, T, length, endpoint=True) 140 | 141 | # plot 142 | if show: 143 | plotting.plot_eeg( 144 | ts=ts, 145 | raw=signal, 146 | filtered=filtered, 147 | labels=labels, 148 | features_ts=ts_feat, 149 | theta=theta, 150 | alpha_low=alpha_low, 151 | alpha_high=alpha_high, 152 | beta=beta, 153 | gamma=gamma, 154 | plf_pairs=plf_pairs, 155 | plf=plf, 156 | path=path, 157 | show=True, 158 | ) 159 | 160 | # output 161 | args = ( 162 | ts, 163 | filtered, 164 | ts_feat, 165 | theta, 166 | alpha_low, 167 | alpha_high, 168 | beta, 169 | gamma, 170 | plf_pairs, 171 | plf, 172 | ) 173 | names = ( 174 | "ts", 175 | "filtered", 176 | "features_ts", 177 | "theta", 178 | "alpha_low", 179 | "alpha_high", 180 | "beta", 181 | "gamma", 182 | "plf_pairs", 183 | "plf", 184 | ) 185 | 186 | return utils.ReturnTuple(args, names) 187 | 188 | 189 | def car_reference(signal=None): 190 | """Change signal reference to the Common Average Reference (CAR). 191 | 192 | Parameters 193 | ---------- 194 | signal : array 195 | Input EEG signal matrix; each column is one EEG channel. 196 | 197 | Returns 198 | ------- 199 | signal : array 200 | Re-referenced EEG signal matrix; each column is one EEG channel. 201 | 202 | """ 203 | 204 | # check inputs 205 | if signal is None: 206 | raise TypeError("Please specify an input signal.") 207 | 208 | length, nch = signal.shape 209 | avg = np.mean(signal, axis=1) 210 | 211 | out = signal - np.tile(avg.reshape((length, 1)), nch) 212 | 213 | return utils.ReturnTuple((out,), ("signal",)) 214 | 215 | 216 | def get_power_features(signal=None, sampling_rate=1000.0, size=0.25, overlap=0.5): 217 | """Extract band power features from EEG signals. 218 | 219 | Computes the average signal power, with overlapping windows, in typical 220 | EEG frequency bands: 221 | * Theta: from 4 to 8 Hz, 222 | * Lower Alpha: from 8 to 10 Hz, 223 | * Higher Alpha: from 10 to 13 Hz, 224 | * Beta: from 13 to 25 Hz, 225 | * Gamma: from 25 to 40 Hz. 226 | 227 | Parameters 228 | ---------- 229 | signal array 230 | Filtered EEG signal matrix; each column is one EEG channel. 231 | sampling_rate : int, float, optional 232 | Sampling frequency (Hz). 233 | size : float, optional 234 | Window size (seconds). 235 | overlap : float, optional 236 | Window overlap (0 to 1). 237 | 238 | Returns 239 | ------- 240 | ts : array 241 | Features time axis reference (seconds). 242 | theta : array 243 | Average power in the 4 to 8 Hz frequency band; each column is one EEG 244 | channel. 245 | alpha_low : array 246 | Average power in the 8 to 10 Hz frequency band; each column is one EEG 247 | channel. 248 | alpha_high : array 249 | Average power in the 10 to 13 Hz frequency band; each column is one EEG 250 | channel. 251 | beta : array 252 | Average power in the 13 to 25 Hz frequency band; each column is one EEG 253 | channel. 254 | gamma : array 255 | Average power in the 25 to 40 Hz frequency band; each column is one EEG 256 | channel. 257 | 258 | """ 259 | 260 | # check inputs 261 | if signal is None: 262 | raise TypeError("Please specify an input signal.") 263 | 264 | # ensure numpy 265 | signal = np.array(signal) 266 | nch = signal.shape[1] 267 | 268 | sampling_rate = float(sampling_rate) 269 | 270 | # convert sizes to samples 271 | size = int(size * sampling_rate) 272 | step = size - int(overlap * size) 273 | 274 | # padding 275 | min_pad = 1024 276 | pad = None 277 | if size < min_pad: 278 | pad = min_pad - size 279 | 280 | # frequency bands 281 | bands = [[4, 8], [8, 10], [10, 13], [13, 25], [25, 40]] 282 | nb = len(bands) 283 | 284 | # windower 285 | fcn_kwargs = {"sampling_rate": sampling_rate, "bands": bands, "pad": pad} 286 | index, values = st.windower( 287 | signal=signal, 288 | size=size, 289 | step=step, 290 | kernel="hann", 291 | fcn=_power_features, 292 | fcn_kwargs=fcn_kwargs, 293 | ) 294 | 295 | # median filter 296 | md_size = int(0.625 * sampling_rate / float(step)) 297 | if md_size % 2 == 0: 298 | # must be odd 299 | md_size += 1 300 | 301 | for i in range(nb): 302 | for j in range(nch): 303 | values[:, i, j], _ = st.smoother( 304 | signal=values[:, i, j], kernel="median", size=md_size 305 | ) 306 | 307 | # extract individual bands 308 | theta = values[:, 0, :] 309 | alpha_low = values[:, 1, :] 310 | alpha_high = values[:, 2, :] 311 | beta = values[:, 3, :] 312 | gamma = values[:, 4, :] 313 | 314 | # convert indices to seconds 315 | ts = index.astype("float") / sampling_rate 316 | 317 | # output 318 | args = (ts, theta, alpha_low, alpha_high, beta, gamma) 319 | names = ("ts", "theta", "alpha_low", "alpha_high", "beta", "gamma") 320 | 321 | return utils.ReturnTuple(args, names) 322 | 323 | 324 | def get_plf_features(signal=None, sampling_rate=1000.0, size=0.25, overlap=0.5): 325 | """Extract Phase-Locking Factor (PLF) features from EEG signals between all 326 | channel pairs. 327 | 328 | Parameters 329 | ---------- 330 | signal : array 331 | Filtered EEG signal matrix; each column is one EEG channel. 332 | sampling_rate : int, float, optional 333 | Sampling frequency (Hz). 334 | size : float, optional 335 | Window size (seconds). 336 | overlap : float, optional 337 | Window overlap (0 to 1). 338 | 339 | Returns 340 | ------- 341 | ts : array 342 | Features time axis reference (seconds). 343 | plf_pairs : list 344 | PLF pair indices. 345 | plf : array 346 | PLF matrix; each column is a channel pair. 347 | 348 | """ 349 | 350 | # check inputs 351 | if signal is None: 352 | raise TypeError("Please specify an input signal.") 353 | 354 | # ensure numpy 355 | signal = np.array(signal) 356 | nch = signal.shape[1] 357 | 358 | sampling_rate = float(sampling_rate) 359 | 360 | # convert sizes to samples 361 | size = int(size * sampling_rate) 362 | step = size - int(overlap * size) 363 | 364 | # padding 365 | min_pad = 1024 366 | N = None 367 | if size < min_pad: 368 | N = min_pad 369 | 370 | # PLF pairs 371 | pairs = [(i, j) for i in range(nch) for j in range(i + 1, nch)] 372 | nb = len(pairs) 373 | 374 | # windower 375 | fcn_kwargs = {"pairs": pairs, "N": N} 376 | index, values = st.windower( 377 | signal=signal, 378 | size=size, 379 | step=step, 380 | kernel="hann", 381 | fcn=_plf_features, 382 | fcn_kwargs=fcn_kwargs, 383 | ) 384 | 385 | # median filter 386 | md_size = int(0.625 * sampling_rate / float(step)) 387 | if md_size % 2 == 0: 388 | # must be odd 389 | md_size += 1 390 | 391 | for i in range(nb): 392 | values[:, i], _ = st.smoother( 393 | signal=values[:, i], kernel="median", size=md_size 394 | ) 395 | 396 | # convert indices to seconds 397 | ts = index.astype("float") / sampling_rate 398 | 399 | # output 400 | args = (ts, pairs, values) 401 | names = ("ts", "plf_pairs", "plf") 402 | 403 | return utils.ReturnTuple(args, names) 404 | 405 | 406 | def _power_features(signal=None, sampling_rate=1000.0, bands=None, pad=0): 407 | """Helper function to compute band power features for each window. 408 | 409 | Parameters 410 | ---------- 411 | signal : array 412 | Filtered EEG signal matrix; each column is one EEG channel. 413 | sampling_rate : int, float, optional 414 | Sampling frequency (Hz). 415 | bands : list 416 | List of frequency pairs defining the bands. 417 | pad : int, optional 418 | Padding for the Fourier Transform (number of zeros added). 419 | 420 | Returns 421 | ------- 422 | out : array 423 | Average power for each band and EEG channel; shape is 424 | (bands, channels). 425 | 426 | """ 427 | 428 | nch = signal.shape[1] 429 | 430 | out = np.zeros((len(bands), nch), dtype="float") 431 | for i in range(nch): 432 | # compute power spectrum 433 | freqs, power = st.power_spectrum( 434 | signal=signal[:, i], 435 | sampling_rate=sampling_rate, 436 | pad=pad, 437 | pow2=False, 438 | decibel=False, 439 | ) 440 | 441 | # compute average band power 442 | for j, b in enumerate(bands): 443 | (avg,) = st.band_power(freqs=freqs, power=power, frequency=b, decibel=False) 444 | out[j, i] = avg 445 | 446 | return out 447 | 448 | 449 | def _plf_features(signal=None, pairs=None, N=None): 450 | """Helper function to compute PLF features for each window. 451 | 452 | Parameters 453 | ---------- 454 | signal : array 455 | Filtered EEG signal matrix; each column is one EEG channel. 456 | pairs : iterable 457 | List of signal channel pairs. 458 | N : int, optional 459 | Number of Fourier components. 460 | 461 | Returns 462 | ------- 463 | out : array 464 | PLF for each channel pair. 465 | 466 | """ 467 | 468 | out = np.zeros(len(pairs), dtype="float") 469 | for i, p in enumerate(pairs): 470 | # compute PLF 471 | s1 = signal[:, p[0]] 472 | s2 = signal[:, p[1]] 473 | (out[i],) = st.phase_locking(signal1=s1, signal2=s2, N=N) 474 | 475 | return out 476 | -------------------------------------------------------------------------------- /biosppy/signals/pcg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals.pcg 4 | ------------------- 5 | 6 | This module provides methods to process Phonocardiography (PCG) signals. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # Imports 13 | # compat 14 | from __future__ import absolute_import, division, print_function 15 | 16 | # 3rd party 17 | import numpy as np 18 | import scipy.signal as ss 19 | 20 | # local 21 | from . import tools as st 22 | from .. import plotting, utils 23 | 24 | 25 | def pcg(signal=None, sampling_rate=1000., path=None, show=True): 26 | """ 27 | 28 | Parameters 29 | ---------- 30 | signal : array 31 | Raw PCG signal. 32 | sampling_rate : int, float, optional 33 | Sampling frequency (Hz). 34 | path : str, optional 35 | If provided, the plot will be saved to the specified file. 36 | show : bool, optional 37 | If True, show a summary plot. 38 | 39 | Returns 40 | ------- 41 | ts : array 42 | Signal time axis reference (seconds). 43 | filtered : array 44 | Filtered PCG signal. 45 | peaks : array 46 | Peak location indices. 47 | hs: array 48 | Classification of peaks as S1 or S2. 49 | heart_rate : double 50 | Average heart rate (bpm). 51 | systolic_time_interval : double 52 | Average systolic time interval (seconds). 53 | heart_rate_ts : array 54 | Heart rate time axis reference (seconds). 55 | inst_heart_rate : array 56 | Instantaneous heart rate (bpm). 57 | 58 | """ 59 | 60 | # check inputs 61 | if signal is None: 62 | raise TypeError("Please specify an input signal.") 63 | 64 | # ensure numpy 65 | signal = np.array(signal) 66 | 67 | sampling_rate = float(sampling_rate) 68 | 69 | # Filter Design 70 | order = 2 71 | passBand = np.array([25, 400]) 72 | 73 | # Band-Pass filtering of the PCG: 74 | filtered,fs,params = st.filter_signal(signal,'butter','bandpass',order,passBand,sampling_rate) 75 | 76 | # find peaks 77 | peaks,envelope = find_peaks(signal=filtered, sampling_rate=sampling_rate) 78 | 79 | # classify heart sounds 80 | hs, = identify_heart_sounds(beats=peaks, sampling_rate=sampling_rate) 81 | s1_peaks = peaks[np.where(hs==1)[0]] 82 | 83 | # get heart rate 84 | heartRate,systolicTimeInterval = get_avg_heart_rate(envelope,sampling_rate) 85 | 86 | # get instantaneous heart rate 87 | hr_idx,hr = st.get_heart_rate(s1_peaks, sampling_rate) 88 | 89 | # get time vectors 90 | length = len(signal) 91 | T = (length - 1) / sampling_rate 92 | ts = np.linspace(0, T, length, endpoint=True) 93 | ts_hr = ts[hr_idx] 94 | 95 | # plot 96 | if show: 97 | plotting.plot_pcg(ts=ts, 98 | raw=signal, 99 | filtered=filtered, 100 | peaks=peaks, 101 | heart_sounds=hs, 102 | heart_rate_ts=ts_hr, 103 | inst_heart_rate=hr, 104 | path=path, 105 | show=True) 106 | 107 | 108 | # output 109 | args = (ts, filtered, peaks, hs, heartRate, systolicTimeInterval, ts_hr, hr) 110 | names = ('ts', 'filtered', 'peaks', 'heart_sounds', 111 | 'heart_rate', 'systolic_time_interval','heart_rate_ts','inst_heart_rate') 112 | 113 | return utils.ReturnTuple(args, names) 114 | 115 | def find_peaks(signal=None,sampling_rate=1000.): 116 | 117 | """Finds the peaks of the heart sounds from the homomorphic envelope 118 | 119 | Parameters 120 | ---------- 121 | signal : array 122 | Input filtered PCG signal. 123 | sampling_rate : int, float, optional 124 | Sampling frequency (Hz). 125 | 126 | Returns 127 | ------- 128 | peaks : array 129 | peak location indices. 130 | envelope : array 131 | Homomorphic envelope (normalized). 132 | 133 | """ 134 | 135 | # Compute homomorphic envelope 136 | envelope, = homomorphic_filter(signal,sampling_rate) 137 | envelope, = st.normalize(envelope) 138 | 139 | # Find the prominent peaks of the envelope 140 | peaksIndices, _ = ss.find_peaks(envelope, height = 0.2 * np.amax(envelope), distance = 0.10*sampling_rate, prominence = 0.25) 141 | 142 | peaks = np.array(peaksIndices, dtype='int') 143 | 144 | return utils.ReturnTuple((peaks,envelope), 145 | ('peaks','homomorphic_envelope')) 146 | 147 | def homomorphic_filter(signal=None, sampling_rate=1000.): 148 | 149 | """Finds the homomorphic envelope of a signal 150 | 151 | Follows the approach described by Schmidt et al. [Schimdt10]_. 152 | 153 | Parameters 154 | ---------- 155 | signal : array 156 | Input filtered PCG signal. 157 | sampling_rate : int, float, optional 158 | Sampling frequency (Hz). 159 | 160 | Returns 161 | ------- 162 | envelope : array 163 | Homomorphic envelope (non-normalized). 164 | 165 | References 166 | ---------- 167 | .. [Schimdt10] S. E. Schmidt et al., "Segmentation of heart sound recordings by a 168 | duration-dependent hidden Markov model", Physiol. Meas., 2010 169 | 170 | """ 171 | 172 | # check inputs 173 | if signal is None: 174 | raise TypeError("Please specify an input signal.") 175 | 176 | sampling_rate = float(sampling_rate) 177 | 178 | # LP-filter Design (to reject the oscillating component of the signal): 179 | order = 1; fc = 8 180 | sos = ss.butter(order, fc, btype = 'low', analog = False, output = 'sos', fs = sampling_rate) 181 | envelope = np.exp( ss.sosfiltfilt(sos, np.log(np.abs(signal)))) 182 | 183 | return utils.ReturnTuple((envelope,), 184 | ('homomorphic_envelope',)) 185 | 186 | def get_avg_heart_rate(envelope=None, sampling_rate=1000.): 187 | 188 | """Compute average heart rate from the signal's homomorphic envelope. 189 | 190 | Follows the approach described by Schmidt et al. [Schimdt10]_, with 191 | code adapted from David Springer [Springer16]_. 192 | 193 | Parameters 194 | ---------- 195 | envelope : array 196 | Signal's homomorphic envelope 197 | sampling_rate : int, float, optional 198 | Sampling frequency (Hz). 199 | 200 | Returns 201 | ------- 202 | heart_rate : double 203 | Average heart rate (bpm). 204 | systolic_time_interval : double 205 | Average systolic time interval (seconds). 206 | 207 | Notes 208 | ----- 209 | * Assumes normal human heart rate to be between 40 and 200 bpm. 210 | * Assumes normal human systole time interval to be between 0.2 seconds and half a heartbeat 211 | 212 | References 213 | ---------- 214 | .. [Schimdt10] S. E. Schmidt et al., "Segmentation of heart sound recordings by a 215 | duration-dependent hidden Markov model", Physiol. Meas., 2010 216 | .. [Springer16] D.Springer, "Heart sound segmentation code based on duration-dependant 217 | HMM", 2016. Available at: https://github.com/davidspringer/Springer-Segmentation-Code 218 | 219 | """ 220 | 221 | # check inputs 222 | if envelope is None: 223 | raise TypeError("Please specify the signal's homomorphic envelope.") 224 | 225 | autocorrelation = np.correlate(envelope,envelope,mode='full') 226 | autocorrelation = autocorrelation[(autocorrelation.size)//2:] 227 | 228 | min_index = int(0.3*sampling_rate) 229 | max_index = int(1.5*sampling_rate) 230 | 231 | index = np.argmax(autocorrelation[min_index-1:max_index-1]) 232 | true_index = index+min_index-1 233 | heartRate = 60/(true_index/sampling_rate) 234 | 235 | max_sys_duration = int(np.round(((60/heartRate)*sampling_rate)/2)) 236 | min_sys_duration = int(np.round(0.2*sampling_rate)) 237 | 238 | pos = np.argmax(autocorrelation[min_sys_duration-1:max_sys_duration-1]) 239 | systolicTimeInterval = (min_sys_duration+pos)/sampling_rate 240 | 241 | 242 | return utils.ReturnTuple((heartRate,systolicTimeInterval), 243 | ('heart_rate','systolic_time_interval')) 244 | 245 | def identify_heart_sounds(beats = None, sampling_rate = 1000.): 246 | 247 | """Classify heart sound peaks as S1 or S2 248 | 249 | Parameters 250 | ---------- 251 | beats : array 252 | Peaks of heart sounds 253 | sampling_rate : int, float, optional 254 | Sampling frequency (Hz). 255 | 256 | Returns 257 | ------- 258 | classification : array 259 | Classification of heart sound peaks. 1 is S1, 2 is S2 260 | 261 | """ 262 | 263 | one_peak_ahead = np.roll(beats, -1) 264 | 265 | SS_intervals = (one_peak_ahead[0:-1] - beats[0:-1]) / sampling_rate 266 | 267 | # Initialize the vector to store the classification of the peaks: 268 | classification = np.zeros(len(beats)) 269 | 270 | # Classify the peaks. 271 | # Terrible algorithm, but good enough for now 272 | for i in range(1,len(beats)-1): 273 | if SS_intervals[i-1] > SS_intervals[i]: 274 | classification[i] = 0 275 | else: 276 | classification[i] = 1 277 | classification[0] = int(not(classification[1])) 278 | classification[-1] = int(not(classification[-2])) 279 | 280 | classification += 1 281 | 282 | return utils.ReturnTuple((classification,), ('heart_sounds',)) -------------------------------------------------------------------------------- /biosppy/signals/resp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals.resp 4 | -------------------- 5 | 6 | This module provides methods to process Respiration (Resp) signals. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # Imports 13 | # compat 14 | from __future__ import absolute_import, division, print_function 15 | 16 | # 3rd party 17 | import numpy as np 18 | 19 | # local 20 | from . import tools as st 21 | from .. import plotting, utils 22 | 23 | 24 | def resp(signal=None, sampling_rate=1000., path=None, show=True): 25 | """Process a raw Respiration signal and extract relevant signal features 26 | using default parameters. 27 | 28 | Parameters 29 | ---------- 30 | signal : array 31 | Raw Respiration signal. 32 | sampling_rate : int, float, optional 33 | Sampling frequency (Hz). 34 | path : str, optional 35 | If provided, the plot will be saved to the specified file. 36 | show : bool, optional 37 | If True, show a summary plot. 38 | 39 | Returns 40 | ------- 41 | ts : array 42 | Signal time axis reference (seconds). 43 | filtered : array 44 | Filtered Respiration signal. 45 | zeros : array 46 | Indices of Respiration zero crossings. 47 | resp_rate_ts : array 48 | Respiration rate time axis reference (seconds). 49 | resp_rate : array 50 | Instantaneous respiration rate (Hz). 51 | 52 | """ 53 | 54 | # check inputs 55 | if signal is None: 56 | raise TypeError("Please specify an input signal.") 57 | 58 | # ensure numpy 59 | signal = np.array(signal) 60 | 61 | sampling_rate = float(sampling_rate) 62 | 63 | # filter signal 64 | filtered, _, _ = st.filter_signal(signal=signal, 65 | ftype='butter', 66 | band='bandpass', 67 | order=2, 68 | frequency=[0.1, 0.35], 69 | sampling_rate=sampling_rate) 70 | 71 | # compute zero crossings 72 | zeros, = st.zero_cross(signal=filtered, detrend=True) 73 | beats = zeros[::2] 74 | 75 | if len(beats) < 2: 76 | rate_idx = [] 77 | rate = [] 78 | else: 79 | # compute respiration rate 80 | rate_idx = beats[1:] 81 | rate = sampling_rate * (1. / np.diff(beats)) 82 | 83 | # physiological limits 84 | indx = np.nonzero(rate <= 0.35) 85 | rate_idx = rate_idx[indx] 86 | rate = rate[indx] 87 | 88 | # smooth with moving average 89 | size = 3 90 | rate, _ = st.smoother(signal=rate, 91 | kernel='boxcar', 92 | size=size, 93 | mirror=True) 94 | 95 | # get time vectors 96 | length = len(signal) 97 | T = (length - 1) / sampling_rate 98 | ts = np.linspace(0, T, length, endpoint=True) 99 | ts_rate = ts[rate_idx] 100 | 101 | # plot 102 | if show: 103 | plotting.plot_resp(ts=ts, 104 | raw=signal, 105 | filtered=filtered, 106 | zeros=zeros, 107 | resp_rate_ts=ts_rate, 108 | resp_rate=rate, 109 | path=path, 110 | show=True) 111 | 112 | # output 113 | args = (ts, filtered, zeros, ts_rate, rate) 114 | names = ('ts', 'filtered', 'zeros', 'resp_rate_ts', 'resp_rate') 115 | 116 | return utils.ReturnTuple(args, names) 117 | -------------------------------------------------------------------------------- /biosppy/stats.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.stats 4 | --------------- 5 | 6 | This module provides statistica functions and related tools. 7 | 8 | :copyright: (c) 2015-2021 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # Imports 13 | # compat 14 | from __future__ import absolute_import, division, print_function 15 | import six 16 | 17 | # local 18 | 19 | from . import utils 20 | 21 | # 3rd party 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | from scipy.stats import pearsonr, ttest_rel, ttest_ind 25 | 26 | 27 | def pearson_correlation(x=None, y=None): 28 | """Compute the Pearson Correlation Coefficient between two signals. 29 | 30 | The coefficient is given by: 31 | 32 | .. math:: 33 | 34 | r_{xy} = \\frac{E[(X - \\mu_X) (Y - \\mu_Y)]}{\\sigma_X \\sigma_Y} 35 | 36 | Parameters 37 | ---------- 38 | x : array 39 | First input signal. 40 | y : array 41 | Second input signal. 42 | 43 | Returns 44 | ------- 45 | r : float 46 | Pearson correlation coefficient, ranging between -1 and +1. 47 | pvalue : float 48 | Two-tailed p-value. The p-value roughly indicates the probability of 49 | an uncorrelated system producing datasets that have a Pearson correlation 50 | at least as extreme as the one computed from these datasets. 51 | 52 | Raises 53 | ------ 54 | ValueError 55 | If the input signals do not have the same length. 56 | 57 | """ 58 | 59 | # check inputs 60 | if x is None: 61 | raise TypeError("Please specify the first input signal.") 62 | 63 | if y is None: 64 | raise TypeError("Please specify the second input signal.") 65 | 66 | # ensure numpy 67 | x = np.array(x) 68 | y = np.array(y) 69 | 70 | n = len(x) 71 | 72 | if n != len(y): 73 | raise ValueError("Input signals must have the same length.") 74 | 75 | r, pvalue = pearsonr(x, y) 76 | 77 | return r, pvalue 78 | 79 | 80 | def linear_regression(x=None, y=None): 81 | """Plot the linear regression between two signals and get the equation coefficients. 82 | 83 | The linear regression uses the least squares method. 84 | 85 | Parameters 86 | ---------- 87 | x : array 88 | First input signal. 89 | y : array 90 | Second input signal. 91 | 92 | Returns 93 | ------- 94 | coeffs : array 95 | Linear regression coefficients: [m, b]. 96 | 97 | Raises 98 | ------ 99 | ValueError 100 | If the input signals do not have the same length. 101 | 102 | """ 103 | 104 | # check inputs 105 | if x is None: 106 | raise TypeError("Please specify the first input signal.") 107 | 108 | if y is None: 109 | raise TypeError("Please specify the second input signal.") 110 | 111 | # ensure numpy 112 | x = np.array(x) 113 | y = np.array(y) 114 | 115 | n = len(x) 116 | 117 | if n != len(y): 118 | raise ValueError("Input signals must have the same length.") 119 | 120 | coeffs = np.polyfit(x, y, 1) 121 | f = np.poly1d(coeffs) 122 | 123 | x_min = x.min() 124 | x_max = x.max() 125 | 126 | y_min = f(x_min) 127 | y_max = f(x_max) 128 | 129 | plt.scatter(x, y) 130 | plt.plot( 131 | [x_min, x_max], 132 | [y_min, y_max], 133 | c="orange", 134 | label="y={:.3f}x+{:.3f}".format(coeffs[0], coeffs[1]), 135 | ) 136 | plt.title("Linear Regression") 137 | plt.xlabel("x") 138 | plt.ylabel("y") 139 | plt.legend() 140 | 141 | return coeffs 142 | 143 | 144 | def paired_test(x=None, y=None): 145 | """ 146 | Perform the Student's paired t-test on the arrays x and y. 147 | This is a two-sided test for the null hypothesis that 2 related 148 | or repeated samples have identical average (expected) values. 149 | 150 | Parameters 151 | ---------- 152 | x : array 153 | First input signal. 154 | y : array 155 | Second input signal. 156 | 157 | Returns 158 | ------- 159 | statistic : float 160 | t-statistic. The t-statistic is used in a t-test to determine 161 | if you should support or reject the null hypothesis. 162 | pvalue : float 163 | Two-sided p-value. 164 | 165 | Raises 166 | ------ 167 | ValueError 168 | If the input signals do not have the same length. 169 | 170 | """ 171 | 172 | # check inputs 173 | if x is None: 174 | raise TypeError("Please specify the first input signal.") 175 | 176 | if y is None: 177 | raise TypeError("Please specify the second input signal.") 178 | 179 | # ensure numpy 180 | x = np.array(x) 181 | y = np.array(y) 182 | 183 | n = len(x) 184 | 185 | if n != len(y): 186 | raise ValueError("Input signals must have the same length.") 187 | 188 | statistic, pvalue = ttest_rel(x, y) 189 | 190 | return statistic, pvalue 191 | 192 | 193 | def unpaired_test(x=None, y=None): 194 | """ 195 | Perform the Student's unpaired t-test on the arrays x and y. 196 | This is a two-sided test for the null hypothesis that 2 independent 197 | samples have identical average (expected) values. This test assumes 198 | that the populations have identical variances by default. 199 | 200 | Parameters 201 | ---------- 202 | x : array 203 | First input signal. 204 | y : array 205 | Second input signal. 206 | 207 | Returns 208 | ------- 209 | statistic : float 210 | t-statistic. The t-statistic is used in a t-test to determine 211 | if you should support or reject the null hypothesis. 212 | pvalue : float 213 | Two-sided p-value. 214 | 215 | Raises 216 | ------ 217 | ValueError 218 | If the input signals do not have the same length. 219 | 220 | """ 221 | 222 | # check inputs 223 | if x is None: 224 | raise TypeError("Please specify the first input signal.") 225 | 226 | if y is None: 227 | raise TypeError("Please specify the second input signal.") 228 | 229 | # ensure numpy 230 | x = np.array(x) 231 | y = np.array(y) 232 | 233 | n = len(x) 234 | 235 | if n != len(y): 236 | raise ValueError("Input signals must have the same length.") 237 | 238 | statistic, pvalue = ttest_ind(x, y) 239 | 240 | return statistic, pvalue 241 | -------------------------------------------------------------------------------- /biosppy/synthesizers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.signals 4 | --------------- 5 | 6 | This package provides methods to synthesize common 7 | physiological signals (biosignals): 8 | * Electrocardiogram (ECG) 9 | 10 | :copyright: (c) 2015-2021 by Instituto de Telecomunicacoes 11 | :license: BSD 3-clause, see LICENSE for more details. 12 | """ 13 | 14 | # compat 15 | from __future__ import absolute_import, division, print_function 16 | 17 | # allow lazy loading 18 | from . import ecg 19 | -------------------------------------------------------------------------------- /biosppy/timing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.timing 4 | -------------- 5 | 6 | This module provides simple methods to measure computation times. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # Imports 13 | # compat 14 | from __future__ import absolute_import, division, print_function 15 | # from six.moves import map, range, zip 16 | # import six 17 | 18 | # built-in 19 | import time 20 | 21 | # 3rd party 22 | 23 | # local 24 | 25 | # Globals 26 | CLOCKS = dict() 27 | DFC = '__default_clock__' 28 | 29 | 30 | def tic(name=None): 31 | """Start the clock. 32 | 33 | Parameters 34 | ---------- 35 | name : str, optional 36 | Name of the clock; if None, uses the default name. 37 | 38 | """ 39 | 40 | if name is None: 41 | name = DFC 42 | 43 | CLOCKS[name] = time.time() 44 | 45 | 46 | def tac(name=None): 47 | """Stop the clock. 48 | 49 | Parameters 50 | ---------- 51 | name : str, optional 52 | Name of the clock; if None, uses the default name. 53 | 54 | Returns 55 | ------- 56 | delta : float 57 | Elapsed time, in seconds. 58 | 59 | Raises 60 | ------ 61 | KeyError if the name of the clock is unknown. 62 | 63 | """ 64 | 65 | toc = time.time() 66 | 67 | if name is None: 68 | name = DFC 69 | 70 | try: 71 | delta = toc - CLOCKS[name] 72 | except KeyError: 73 | raise KeyError('Unknown clock.') 74 | 75 | return delta 76 | 77 | 78 | def clear(name=None): 79 | """Clear the clock. 80 | 81 | Parameters 82 | ---------- 83 | name : str, optional 84 | Name of the clock; if None, uses the default name. 85 | 86 | """ 87 | 88 | if name is None: 89 | name = DFC 90 | 91 | CLOCKS.pop(name) 92 | 93 | 94 | def clear_all(): 95 | """Clear all clocks.""" 96 | 97 | CLOCKS.clear() 98 | -------------------------------------------------------------------------------- /biosppy/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | biosppy.utils 4 | ------------- 5 | 6 | This module provides several frequently used functions and hacks. 7 | 8 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 9 | :license: BSD 3-clause, see LICENSE for more details. 10 | """ 11 | 12 | # Imports 13 | # compat 14 | from __future__ import absolute_import, division, print_function 15 | from six.moves import map, range, zip 16 | import six 17 | 18 | # built-in 19 | import collections 20 | import copy 21 | import keyword 22 | import os 23 | import re 24 | 25 | # 3rd party 26 | import numpy as np 27 | 28 | 29 | def normpath(path): 30 | """Normalize a path. 31 | 32 | Parameters 33 | ---------- 34 | path : str 35 | The path to normalize. 36 | 37 | Returns 38 | ------- 39 | npath : str 40 | The normalized path. 41 | 42 | """ 43 | 44 | if "~" in path: 45 | out = os.path.abspath(os.path.expanduser(path)) 46 | else: 47 | out = os.path.abspath(path) 48 | 49 | return out 50 | 51 | 52 | def fileparts(path): 53 | """split a file path into its directory, name, and extension. 54 | 55 | Parameters 56 | ---------- 57 | path : str 58 | Input file path. 59 | 60 | Returns 61 | ------- 62 | dirname : str 63 | File directory. 64 | fname : str 65 | File name. 66 | ext : str 67 | File extension. 68 | 69 | Notes 70 | ----- 71 | * Removes the dot ('.') from the extension. 72 | 73 | """ 74 | 75 | dirname, fname = os.path.split(path) 76 | fname, ext = os.path.splitext(fname) 77 | ext = ext.replace(".", "") 78 | 79 | return dirname, fname, ext 80 | 81 | 82 | def fullfile(*args): 83 | """Join one or more file path components, assuming the last is 84 | the extension. 85 | 86 | Parameters 87 | ---------- 88 | ``*args`` : list, optional 89 | Components to concatenate. 90 | 91 | Returns 92 | ------- 93 | fpath : str 94 | The concatenated file path. 95 | 96 | """ 97 | 98 | nb = len(args) 99 | if nb == 0: 100 | return "" 101 | elif nb == 1: 102 | return args[0] 103 | elif nb == 2: 104 | return os.path.join(*args) 105 | 106 | fpath = os.path.join(*args[:-1]) + "." + args[-1] 107 | 108 | return fpath 109 | 110 | 111 | def walktree(top=None, spec=None): 112 | """Iterator to recursively descend a directory and return all files 113 | matching the spec. 114 | 115 | Parameters 116 | ---------- 117 | top : str, optional 118 | Starting directory; if None, defaults to the current working directoty. 119 | spec : str, optional 120 | Regular expression to match the desired files; 121 | if None, matches all files; typical patterns: 122 | * `r'\.txt$'` - matches files with '.txt' extension; 123 | * `r'^File_'` - matches files starting with 'File\_' 124 | * `r'^File_.+\.txt$'` - matches files starting with 'File\_' and ending with the '.txt' extension. 125 | 126 | Yields 127 | ------ 128 | fpath : str 129 | Absolute file path. 130 | 131 | Notes 132 | ----- 133 | * Partial matches are also selected. 134 | 135 | See Also 136 | -------- 137 | * https://docs.python.org/3/library/re.html 138 | * https://regex101.com/ 139 | 140 | """ 141 | 142 | if top is None: 143 | top = os.getcwd() 144 | 145 | if spec is None: 146 | spec = r".*?" 147 | 148 | prog = re.compile(spec) 149 | 150 | for root, _, files in os.walk(top): 151 | for name in files: 152 | if prog.search(name): 153 | fname = os.path.join(root, name) 154 | yield fname 155 | 156 | 157 | def remainderAllocator(votes, k, reverse=True, check=False): 158 | """Allocate k seats proportionally using the Remainder Method. 159 | 160 | Also known as Hare-Niemeyer Method. Uses the Hare quota. 161 | 162 | Parameters 163 | ---------- 164 | votes : list 165 | Number of votes for each class/party/cardinal. 166 | k : int 167 | Total number o seats to allocate. 168 | reverse : bool, optional 169 | If True, allocates remaining seats largest quota first. 170 | check : bool, optional 171 | If True, limits the number of seats to the total number of votes. 172 | 173 | Returns 174 | ------- 175 | seats : list 176 | Number of seats for each class/party/cardinal. 177 | 178 | """ 179 | 180 | # check total number of votes 181 | tot = np.sum(votes) 182 | if check and k > tot: 183 | k = tot 184 | 185 | # frequencies 186 | length = len(votes) 187 | freqs = np.array(votes, dtype="float") / tot 188 | 189 | # assign items 190 | aux = k * freqs 191 | seats = aux.astype("int") 192 | 193 | # leftovers 194 | nb = k - seats.sum() 195 | if nb > 0: 196 | if reverse: 197 | ind = np.argsort(aux - seats)[::-1] 198 | else: 199 | ind = np.argsort(aux - seats) 200 | 201 | for i in range(nb): 202 | seats[ind[i % length]] += 1 203 | 204 | return seats.tolist() 205 | 206 | 207 | def highestAveragesAllocator(votes, k, divisor="dHondt", check=False): 208 | """Allocate k seats proportionally using the Highest Averages Method. 209 | 210 | Parameters 211 | ---------- 212 | votes : list 213 | Number of votes for each class/party/cardinal. 214 | k : int 215 | Total number o seats to allocate. 216 | divisor : str, optional 217 | Divisor method; one of 'dHondt', 'Huntington-Hill', 'Sainte-Lague', 218 | 'Imperiali', or 'Danish'. 219 | check : bool, optional 220 | If True, limits the number of seats to the total number of votes. 221 | 222 | Returns 223 | ------- 224 | seats : list 225 | Number of seats for each class/party/cardinal. 226 | 227 | """ 228 | 229 | # check total number of cardinals 230 | tot = np.sum(votes) 231 | if check and k > tot: 232 | k = tot 233 | 234 | # select divisor 235 | if divisor == "dHondt": 236 | fcn = lambda i: float(i) 237 | elif divisor == "Huntington-Hill": 238 | fcn = lambda i: np.sqrt(i * (i + 1.0)) 239 | elif divisor == "Sainte-Lague": 240 | fcn = lambda i: i - 0.5 241 | elif divisor == "Imperiali": 242 | fcn = lambda i: float(i + 1) 243 | elif divisor == "Danish": 244 | fcn = lambda i: 3.0 * (i - 1.0) + 1.0 245 | else: 246 | raise ValueError("Unknown divisor method.") 247 | 248 | # compute coefficients 249 | tab = [] 250 | length = len(votes) 251 | D = [fcn(i) for i in range(1, k + 1)] 252 | for i in range(length): 253 | for j in range(k): 254 | tab.append((i, votes[i] / D[j])) 255 | 256 | # sort 257 | tab.sort(key=lambda item: item[1], reverse=True) 258 | tab = tab[:k] 259 | tab = np.array([item[0] for item in tab], dtype="int") 260 | 261 | seats = np.zeros(length, dtype="int") 262 | for i in range(length): 263 | seats[i] = np.sum(tab == i) 264 | 265 | return seats.tolist() 266 | 267 | 268 | def random_fraction(indx, fraction, sort=True): 269 | """Select a random fraction of an input list of elements. 270 | 271 | Parameters 272 | ---------- 273 | indx : list, array 274 | Elements to partition. 275 | fraction : int, float 276 | Fraction to select. 277 | sort : bool, optional 278 | If True, output lists will be sorted. 279 | 280 | Returns 281 | ------- 282 | use : list, array 283 | Selected elements. 284 | unuse : list, array 285 | Remaining elements. 286 | 287 | """ 288 | 289 | # number of elements to use 290 | fraction = float(fraction) 291 | nb = int(fraction * len(indx)) 292 | 293 | # copy because shuffle works in place 294 | aux = copy.deepcopy(indx) 295 | 296 | # shuffle 297 | np.random.shuffle(aux) 298 | 299 | # select 300 | use = aux[:nb] 301 | unuse = aux[nb:] 302 | 303 | # sort 304 | if sort: 305 | use.sort() 306 | unuse.sort() 307 | 308 | return use, unuse 309 | 310 | 311 | class ReturnTuple(tuple): 312 | """A named tuple to use as a hybrid tuple-dict return object. 313 | 314 | Parameters 315 | ---------- 316 | values : iterable 317 | Return values. 318 | names : iterable, optional 319 | Names for return values. 320 | 321 | Raises 322 | ------ 323 | ValueError 324 | If the number of values differs from the number of names. 325 | ValueError 326 | If any of the items in names: 327 | * contain non-alphanumeric characters; 328 | * are Python keywords; 329 | * start with a number; 330 | * are duplicates. 331 | 332 | """ 333 | 334 | def __new__(cls, values, names=None): 335 | 336 | return tuple.__new__(cls, tuple(values)) 337 | 338 | def __init__(self, values, names=None): 339 | 340 | nargs = len(values) 341 | 342 | if names is None: 343 | # create names 344 | names = ["_%d" % i for i in range(nargs)] 345 | else: 346 | # check length 347 | if len(names) != nargs: 348 | raise ValueError("Number of names and values mismatch.") 349 | 350 | # convert to str 351 | names = list(map(str, names)) 352 | 353 | # check for keywords, alphanumeric, digits, repeats 354 | seen = set() 355 | for name in names: 356 | if not all(c.isalnum() or (c == "_") for c in name): 357 | raise ValueError( 358 | "Names can only contain alphanumeric \ 359 | characters and underscores: %r." 360 | % name 361 | ) 362 | 363 | if keyword.iskeyword(name): 364 | raise ValueError("Names cannot be a keyword: %r." % name) 365 | 366 | if name[0].isdigit(): 367 | raise ValueError("Names cannot start with a number: %r." % name) 368 | 369 | if name in seen: 370 | raise ValueError("Encountered duplicate name: %r." % name) 371 | 372 | seen.add(name) 373 | 374 | self._names = names 375 | 376 | def as_dict(self): 377 | """Convert to an ordered dictionary. 378 | 379 | Returns 380 | ------- 381 | out : OrderedDict 382 | An OrderedDict representing the return values. 383 | 384 | """ 385 | 386 | return collections.OrderedDict(zip(self._names, self)) 387 | 388 | __dict__ = property(as_dict) 389 | 390 | def __getitem__(self, key): 391 | """Get item as an index or keyword. 392 | 393 | Returns 394 | ------- 395 | out : object 396 | The object corresponding to the key, if it exists. 397 | 398 | Raises 399 | ------ 400 | KeyError 401 | If the key is a string and it does not exist in the mapping. 402 | IndexError 403 | If the key is an int and it is out of range. 404 | 405 | """ 406 | 407 | if isinstance(key, six.string_types): 408 | if key not in self._names: 409 | raise KeyError("Unknown key: %r." % key) 410 | 411 | key = self._names.index(key) 412 | 413 | return super(ReturnTuple, self).__getitem__(key) 414 | 415 | def __repr__(self): 416 | """Return representation string.""" 417 | 418 | tpl = "%s=%r" 419 | 420 | rp = ", ".join(tpl % item for item in zip(self._names, self)) 421 | 422 | return "ReturnTuple(%s)" % rp 423 | 424 | def __getnewargs__(self): 425 | """Return self as a plain tuple; used for copy and pickle.""" 426 | 427 | return tuple(self) 428 | 429 | def keys(self): 430 | """Return the value names. 431 | 432 | Returns 433 | ------- 434 | out : list 435 | The keys in the mapping. 436 | 437 | """ 438 | 439 | return list(self._names) 440 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BioSPPy.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BioSPPy.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/BioSPPy" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BioSPPy" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/biosppy.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | This part of the documentation details the complete ``BioSPPy`` API. 5 | 6 | Packages 7 | -------- 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | biosppy.signals 13 | 14 | Modules 15 | ------- 16 | 17 | .. contents:: 18 | :local: 19 | 20 | .. automodule:: biosppy.biometrics 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | .. automodule:: biosppy.clustering 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | .. automodule:: biosppy.metrics 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | 35 | .. automodule:: biosppy.plotting 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | 40 | .. automodule:: biosppy.storage 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | .. automodule:: biosppy.timing 46 | :members: 47 | :undoc-members: 48 | :show-inheritance: 49 | 50 | .. automodule:: biosppy.utils 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/biosppy.signals.rst: -------------------------------------------------------------------------------- 1 | biosppy.signals 2 | =============== 3 | 4 | This sub-package provides methods to process common physiological signals 5 | (biosignals). 6 | 7 | Modules 8 | ------- 9 | 10 | .. contents:: 11 | :local: 12 | 13 | .. automodule:: biosppy.signals.abp 14 | :members: 15 | :undoc-members: 16 | :show-inheritance: 17 | 18 | .. automodule:: biosppy.signals.bvp 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | .. automodule:: biosppy.signals.ppg 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | .. automodule:: biosppy.signals.ecg 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | .. automodule:: biosppy.signals.eda 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | .. automodule:: biosppy.signals.eeg 39 | :members: 40 | :undoc-members: 41 | :show-inheritance: 42 | 43 | .. automodule:: biosppy.signals.emg 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | 48 | .. automodule:: biosppy.signals.resp 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | 53 | .. automodule:: biosppy.signals.tools 54 | :members: 55 | :undoc-members: 56 | :show-inheritance: 57 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # BioSPPy documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Aug 18 11:33:55 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | # import os 17 | # import shlex 18 | 19 | # To be able to import to ReadTheDocs 20 | from mock import Mock as MagicMock 21 | 22 | 23 | class Mock(MagicMock): 24 | @classmethod 25 | def __getattr__(cls, name): 26 | return Mock() 27 | 28 | 29 | MOCK_MODULES = ['numpy', 'scipy', 'matplotlib', 'matplotlib.pyplot', 30 | 'scipy.signal', 'scipy.interpolate', 'scipy.optimize', 31 | 'scipy.stats', 'scipy.cluster', 'scipy.cluster.hierarchy', 32 | 'scipy.cluster.vq', 'scipy.sparse', 'scipy.spatial', 33 | 'scipy.spatial.distance', 'sklearn', 'sklearn.cluster', 34 | 'sklearn.model_selection', 'sklearn.externals', 35 | 'matplotlib.gridspec', 'h5py', 'shortuuid', 'bidict', 'svm', 36 | 'sksvm'] 37 | 38 | sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) 39 | 40 | # If extensions (or modules to document with autodoc) are in another directory, 41 | # add these directories to sys.path here. If the directory is relative to the 42 | # documentation root, use os.path.abspath to make it absolute, like shown here. 43 | #sys.path.insert(0, os.path.abspath('.')) 44 | 45 | # -- General configuration ------------------------------------------------ 46 | 47 | # If your documentation needs a minimal Sphinx version, state it here. 48 | #needs_sphinx = '1.0' 49 | 50 | # Add any Sphinx extension module names here, as strings. They can be 51 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 52 | # ones. 53 | extensions = [ 54 | 'sphinx.ext.autodoc', 55 | 'sphinx.ext.coverage', 56 | 'sphinx.ext.viewcode', 57 | 'sphinx.ext.napoleon', 58 | 'sphinx.ext.imgmath', 59 | ] 60 | 61 | # Napoleon settings 62 | napoleon_use_rtype = False 63 | 64 | # Add any paths that contain templates here, relative to this directory. 65 | templates_path = ['_templates'] 66 | 67 | # The suffix(es) of source filenames. 68 | # You can specify multiple suffix as a list of string: 69 | # source_suffix = ['.rst', '.md'] 70 | source_suffix = '.rst' 71 | 72 | # The encoding of source files. 73 | #source_encoding = 'utf-8-sig' 74 | 75 | # The master toctree document. 76 | master_doc = 'index' 77 | 78 | # General information about the project. 79 | project = 'BioSPPy' 80 | copyright = '2015-2018, Instituto de Telecomunicacoes' 81 | author = 'Instituto de Telecomunicacoes' 82 | 83 | # The version info for the project you're documenting, acts as replacement for 84 | # |version| and |release|, also used in various other places throughout the 85 | # built documents. 86 | # 87 | # The short X.Y version. 88 | version = '0.6.1' 89 | # The full version, including alpha/beta/rc tags. 90 | release = version 91 | 92 | # The language for content autogenerated by Sphinx. Refer to documentation 93 | # for a list of supported languages. 94 | # 95 | # This is also used if you do content translation via gettext catalogs. 96 | # Usually you set "language" from the command line for these cases. 97 | language = None 98 | 99 | # There are two options for replacing |today|: either, you set today to some 100 | # non-false value, then it is used: 101 | #today = '' 102 | # Else, today_fmt is used as the format for a strftime call. 103 | #today_fmt = '%B %d, %Y' 104 | 105 | # List of patterns, relative to source directory, that match files and 106 | # directories to ignore when looking for source files. 107 | exclude_patterns = ['_build'] 108 | 109 | # The reST default role (used for this markup: `text`) to use for all 110 | # documents. 111 | #default_role = None 112 | 113 | # If true, '()' will be appended to :func: etc. cross-reference text. 114 | #add_function_parentheses = True 115 | 116 | # If true, the current module name will be prepended to all description 117 | # unit titles (such as .. function::). 118 | #add_module_names = True 119 | 120 | # If true, sectionauthor and moduleauthor directives will be shown in the 121 | # output. They are ignored by default. 122 | #show_authors = False 123 | 124 | # The name of the Pygments (syntax highlighting) style to use. 125 | pygments_style = 'sphinx' 126 | 127 | # A list of ignored prefixes for module index sorting. 128 | #modindex_common_prefix = [] 129 | 130 | # If true, keep warnings as "system message" paragraphs in the built documents. 131 | #keep_warnings = False 132 | 133 | # If true, `todo` and `todoList` produce output, else they produce nothing. 134 | todo_include_todos = False 135 | 136 | 137 | # -- Options for HTML output ---------------------------------------------- 138 | 139 | # The theme to use for HTML and HTML Help pages. See the documentation for 140 | # a list of builtin themes. 141 | html_theme = 'sphinx_rtd_theme' 142 | 143 | # Theme options are theme-specific and customize the look and feel of a theme 144 | # further. For a list of options available for each theme, see the 145 | # documentation. 146 | html_theme_options = { 147 | 'logo_only': True, 148 | } 149 | 150 | # Add any paths that contain custom themes here, relative to this directory. 151 | #html_theme_path = [] 152 | 153 | # The name for this set of Sphinx documents. If None, it defaults to 154 | # " v documentation". 155 | #html_title = None 156 | 157 | # A shorter title for the navigation bar. Default is the same as html_title. 158 | #html_short_title = None 159 | 160 | # The name of an image file (relative to this directory) to place at the top 161 | # of the sidebar. 162 | html_logo = "logo/logo_inverted_no_tag.png" 163 | 164 | # The name of an image file (within the static path) to use as favicon of the 165 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 166 | # pixels large. 167 | html_favicon = "favicon.ico" 168 | 169 | # Add any paths that contain custom static files (such as style sheets) here, 170 | # relative to this directory. They are copied after the builtin static files, 171 | # so a file named "default.css" will overwrite the builtin "default.css". 172 | # html_static_path = ['_static'] 173 | 174 | # Add any extra paths that contain custom files (such as robots.txt or 175 | # .htaccess) here, relative to this directory. These files are copied 176 | # directly to the root of the documentation. 177 | #html_extra_path = [] 178 | 179 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 180 | # using the given strftime format. 181 | #html_last_updated_fmt = '%b %d, %Y' 182 | 183 | # If true, SmartyPants will be used to convert quotes and dashes to 184 | # typographically correct entities. 185 | #html_use_smartypants = True 186 | 187 | # Custom sidebar templates, maps document names to template names. 188 | #html_sidebars = {} 189 | 190 | # Additional templates that should be rendered to pages, maps page names to 191 | # template names. 192 | #html_additional_pages = {} 193 | 194 | # If false, no module index is generated. 195 | #html_domain_indices = True 196 | 197 | # If false, no index is generated. 198 | #html_use_index = True 199 | 200 | # If true, the index is split into individual pages for each letter. 201 | #html_split_index = False 202 | 203 | # If true, links to the reST sources are added to the pages. 204 | html_show_sourcelink = False 205 | 206 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 207 | #html_show_sphinx = True 208 | 209 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 210 | #html_show_copyright = True 211 | 212 | # If true, an OpenSearch description file will be output, and all pages will 213 | # contain a tag referring to it. The value of this option must be the 214 | # base URL from which the finished HTML is served. 215 | #html_use_opensearch = '' 216 | 217 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 218 | #html_file_suffix = None 219 | 220 | # Language to be used for generating the HTML full-text search index. 221 | # Sphinx supports the following languages: 222 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 223 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 224 | #html_search_language = 'en' 225 | 226 | # A dictionary with options for the search language support, empty by default. 227 | # Now only 'ja' uses this config value 228 | #html_search_options = {'type': 'default'} 229 | 230 | # The name of a javascript file (relative to the configuration directory) that 231 | # implements a search results scorer. If empty, the default will be used. 232 | #html_search_scorer = 'scorer.js' 233 | 234 | # Output file base name for HTML help builder. 235 | htmlhelp_basename = 'BioSPPydoc' 236 | 237 | # -- Options for LaTeX output --------------------------------------------- 238 | 239 | latex_elements = { 240 | # The paper size ('letterpaper' or 'a4paper'). 241 | #'papersize': 'letterpaper', 242 | 243 | # The font size ('10pt', '11pt' or '12pt'). 244 | #'pointsize': '10pt', 245 | 246 | # Additional stuff for the LaTeX preamble. 247 | #'preamble': '', 248 | 249 | # Latex figure (float) alignment 250 | #'figure_align': 'htbp', 251 | } 252 | 253 | # Grouping the document tree into LaTeX files. List of tuples 254 | # (source start file, target name, title, 255 | # author, documentclass [howto, manual, or own class]). 256 | latex_documents = [ 257 | (master_doc, 'BioSPPy.tex', 'BioSPPy Documentation', 258 | 'Instituto de Telecomunicacoes', 'manual'), 259 | ] 260 | 261 | # The name of an image file (relative to this directory) to place at the top of 262 | # the title page. 263 | #latex_logo = None 264 | 265 | # For "manual" documents, if this is true, then toplevel headings are parts, 266 | # not chapters. 267 | #latex_use_parts = False 268 | 269 | # If true, show page references after internal links. 270 | #latex_show_pagerefs = False 271 | 272 | # If true, show URL addresses after external links. 273 | #latex_show_urls = False 274 | 275 | # Documents to append as an appendix to all manuals. 276 | #latex_appendices = [] 277 | 278 | # If false, no module index is generated. 279 | #latex_domain_indices = True 280 | 281 | 282 | # -- Options for manual page output --------------------------------------- 283 | 284 | # One entry per manual page. List of tuples 285 | # (source start file, name, description, authors, manual section). 286 | man_pages = [ 287 | (master_doc, 'biosppy', 'BioSPPy Documentation', 288 | [author], 1) 289 | ] 290 | 291 | # If true, show URL addresses after external links. 292 | #man_show_urls = False 293 | 294 | 295 | # -- Options for Texinfo output ------------------------------------------- 296 | 297 | # Grouping the document tree into Texinfo files. List of tuples 298 | # (source start file, target name, title, author, 299 | # dir menu entry, description, category) 300 | texinfo_documents = [ 301 | (master_doc, 'BioSPPy', 'BioSPPy Documentation', 302 | author, 'BioSPPy', 'Biosignal Processing in Python.', 303 | 'Miscellaneous'), 304 | ] 305 | 306 | # Documents to append as an appendix to all manuals. 307 | #texinfo_appendices = [] 308 | 309 | # If false, no module index is generated. 310 | #texinfo_domain_indices = True 311 | 312 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 313 | #texinfo_show_urls = 'footnote' 314 | 315 | # If true, do not generate a @detailmenu in the "Top" node's menu. 316 | #texinfo_no_detailmenu = False 317 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/favicon.ico -------------------------------------------------------------------------------- /docs/images/ECG_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/images/ECG_raw.png -------------------------------------------------------------------------------- /docs/images/ECG_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/images/ECG_summary.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to ``BioSPPy`` 2 | ====================== 3 | 4 | .. image:: logo/logo.png 5 | :align: center 6 | :alt: I know you're listening! 7 | 8 | ``BioSPPy`` is a toolbox for biosignal processing written in Python. 9 | The toolbox bundles together various signal processing and pattern 10 | recognition methods geared torwards the analysis of biosignals. 11 | 12 | Highlights: 13 | 14 | - Support for various biosignals: PPG, ECG, EDA, EEG, EMG, Respiration 15 | - Signal analysis primitives: filtering, frequency analysis 16 | - Clustering 17 | - Biometrics 18 | 19 | Contents: 20 | 21 | .. toctree:: 22 | :maxdepth: 1 23 | 24 | tutorial 25 | biosppy 26 | 27 | Installation 28 | ------------ 29 | 30 | Installation can be easily done with ``pip``: 31 | 32 | .. code:: bash 33 | 34 | $ pip install biosppy 35 | 36 | Simple Example 37 | -------------- 38 | 39 | The code below loads an ECG signal from the ``examples`` folder, filters 40 | it, performs R-peak detection, and computes the instantaneous heart 41 | rate. 42 | 43 | .. code:: python 44 | 45 | import numpy as np 46 | from biosppy.signals import ecg 47 | 48 | # load raw ECG signal 49 | signal = np.loadtxt('./examples/ecg.txt') 50 | 51 | # process it and plot 52 | out = ecg.ecg(signal=signal, sampling_rate=1000., show=True) 53 | 54 | Index 55 | ----- 56 | 57 | * :ref:`genindex` 58 | * :ref:`modindex` 59 | * :ref:`search` 60 | -------------------------------------------------------------------------------- /docs/logo/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/logo/favicon.png -------------------------------------------------------------------------------- /docs/logo/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 27 | 30 | 31 | 34 | 37 | 38 | 45 | 46 | 69 | 71 | 72 | 74 | image/svg+xml 75 | 77 | 78 | 79 | 80 | 81 | 86 | 93 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 148 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 210 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /docs/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/logo/logo.png -------------------------------------------------------------------------------- /docs/logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 27 | 34 | 35 | 38 | 45 | 46 | 49 | 56 | 57 | 58 | 81 | 83 | 84 | 86 | image/svg+xml 87 | 89 | 90 | 91 | 92 | 93 | 98 | 100 | BioS y Biosignal Processing in Python 144 | 149 | 154 | 159 | 166 | 175 | 182 | 187 | 192 | 197 | 205 | 208 | 211 | 218 | 225 | 226 | 231 | 237 | 242 | 243 | 246 | 63 bpm 277 | 278 | 283 | 288 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /docs/logo/logo_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/logo/logo_400.png -------------------------------------------------------------------------------- /docs/logo/logo_inverted_no_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/logo/logo_inverted_no_tag.png -------------------------------------------------------------------------------- /docs/logo/logo_inverted_no_tag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 27 | 34 | 35 | 38 | 45 | 46 | 47 | 70 | 72 | 73 | 75 | image/svg+xml 76 | 78 | 79 | 80 | 81 | 82 | 87 | 94 | 96 | BioS y 130 | 136 | 143 | 152 | 159 | 167 | 170 | 177 | 184 | 189 | 195 | 196 | 200 | 63 bpm 235 | 236 | 241 | 246 | 251 | 256 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /docs/logo/logo_inverted_no_tag_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PIA-Group/BioSPPy/77550221f77e024459840ece540b1e017ed99e57/docs/logo/logo_inverted_no_tag_400.png -------------------------------------------------------------------------------- /docs/logo/make_ico.bat: -------------------------------------------------------------------------------- 1 | magick favicon.png -resize 16x16 fav_16.png 2 | magick favicon.png -resize 31x31 fav_32.png 3 | magick favicon.png -resize 64x64 fav_64.png 4 | magick fav_16.png fav_32.png fav_64.png ../favicon.ico 5 | magick identify ../favicon.ico 6 | del fav_16.png fav_32.png fav_64.png 7 | pause -------------------------------------------------------------------------------- /docs/logo/make_small.bat: -------------------------------------------------------------------------------- 1 | magick logo.png -resize 400x400 logo_400.png 2 | magick logo_inverted_no_tag.png -resize 400x400 logo_inverted_no_tag_400.png 3 | pause -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\BioSPPy.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\BioSPPy.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Tutorial 3 | ======== 4 | 5 | In this tutorial we will describe how `biosppy` enables the development of 6 | Pattern Recognition and Machine Learning workflows for the analysis of 7 | biosignals. The major goal of this package is to make these tools easily 8 | available to anyone wishing to start playing around with biosignal data, 9 | regardless of their level of knowledge in the field of Data Science. Throughout 10 | this tutorial we will discuss the major features of `biosppy` and introduce the 11 | terminology used by the package. 12 | 13 | What are Biosignals? 14 | ==================== 15 | 16 | Biosignals, in the most general sense, are measurements of physical properties 17 | of biological systems. These include the measurement of properties at the 18 | cellular level, such as concentrations of molecules, membrane potentials, and 19 | DNA assays. On a higher level, for a group of specialized cells (i.e. an organ) 20 | we are able to measure properties such as cell counts and histology, organ 21 | secretions, and electrical activity (the electrical system of the heart, for 22 | instance). Finally, for complex biological systems like the human being, 23 | biosignals also include blood and urine test measurements, core body 24 | temperature, motion tracking signals, and imaging techniques such as CAT and MRI 25 | scans. However, the term biosignal is most often applied to bioelectrical, 26 | time-varying signals, such as the electrocardiogram. 27 | 28 | The task of obtaining biosignals of good quality is time-consuming, 29 | and typically requires the use of costly hardware. Access to these instruments 30 | is, therefore, usually restricted to research institutes, medical centers, 31 | and hospitals. However, recent projects like `BITalino `__ 32 | or `OpenBCI `__ have lowered the entry barriers of biosignal 33 | acquisition, fostering the Do-It-Yourself and Maker communities to develop 34 | physiological computing applications. You can find a list of biosignal 35 | platform `here `__. 36 | 37 | 38 | 39 | 40 | 41 | The following sub-sections briefly describe the biosignals 42 | covered by `biosppy`. 43 | 44 | Blood Volume Pulse 45 | ------------------ 46 | 47 | Photoplethysmogram (PPG) signals is an optical technique used to detect blood volume changes 48 | within the microvascular bed of your tissue. A PPG wave is made of a pulsatile physiological 49 | measurement taken at the skin surface. The baseline is made of a superimposed varying baseline 50 | with various lower frequency componenets attributed to respiration, thermoregulation, and 51 | sympathetic nervous system activity. Due to it's low cost and simplicity it can be found within 52 | personal devices such as Smart Watches, Phones, and handheld heart rate monitors. 53 | 54 | Electrocardiogram 55 | ----------------- 56 | 57 | Electrocardiogrm (ECG/EKG) signals are a measure of the electrical heartbeat of the heart. 58 | Each heartbeat an electrical impulse travels through the heart, causing your heart to 59 | pump blood from the heart throughout your body. Often times upto twelve non-invasive 60 | electrodes are attached to your chest and limbs. They record the electrical signals that 61 | result in a heartbeat and output them onto ECG charts either on paper or on a computer. 62 | ECG/EKG signals can be processed in time and frequency domains. A healthy adult ECG/EKG is 63 | often predictable while adults with heart problems are often unpredictable. 64 | 65 | Electrodermal Activity 66 | ---------------------- 67 | 68 | Electrodermal Activity (EDA) signals are measures of the electrical characteristics of the skin 69 | using methods such as skin potential (SP), skin conductance response (SCR), skin potential response (SPR). 70 | Training in EDA allows the patient to become more aware of stress. It is not commonly used 71 | and, when used, it is often in conjunction with other forms of biofeedback. Because EDA 72 | measures only skin changes, it does not provide feedback about more complex physiological 73 | reactions. When used for treatment, it tends to be as a monitoring system for unresolved 74 | issues in psychotherapy or for general stress. 75 | 76 | Electroencephalogram 77 | -------------------- 78 | 79 | Electroencephalogram (EEG) signals are measures of electrical activity in the brain using 80 | electrodes attached to the scalp. Generally the process used to get an EEG is non-invasive. 81 | An EEG measures voltage fluctuations resulting from ionic currents within nuerons, which can 82 | be recorded over a period of time thus allowing for analysis within the time domain. 83 | The recording is obtained by placing electrodes on the scalp with a conductive gel, 84 | usually after preparing the scalp area by light abrasion to reduce impedance due to dead skin cells. 85 | 86 | 87 | Electromyogram 88 | -------------- 89 | 90 | Electromyogram (EMG) signals are a measure of the electrical activity of 91 | muscles. There are two types of sensors that can be used to record this 92 | electrical activity, in particular surface EMG (sEMG), measured by non-invasive 93 | electrodes, and intramuscular EMG. Out of the two, sEMG allows for non-invasive 94 | electrodes to be applied at the body surface, that measure muscle activity. 95 | In sEMG, contact with the skin can be done with standard pre-gelled electrodes, 96 | dry Ag/AgCl electrodes or conductive textiles. Normally, there are three 97 | electrodes in an sEMG interface: two electrodes work on bipolar differential 98 | measurement and the other one is attached to a neutral zone, to serve as the 99 | reference point. After being recorded, this signal can be processed in time, 100 | frequency and time-frequency domains. In an EMG signal, when the muscle is in 101 | a relaxed state, this corresponds to the baseline activity. The bursts of 102 | activity match the muscular activations and have a random shape, meaning that 103 | a raw recording of contractions cannot be exactly reproduced. The onset of an 104 | event corresponds to the beginning of the burst. 105 | 106 | Respiration 107 | ----------- 108 | 109 | Respiration (Resp) signals are... 110 | 111 | 112 | What is Pattern Recognition? 113 | ============================ 114 | 115 | Pattern Recognition is an automated analytical recognition of patterns and 116 | regularities within a piece of data. Often time stastical fields such as 117 | Machine Learning rely on pattern recognition to find similarities within 118 | data in order to predict future data. 119 | 120 | A Note on Return Objects 121 | ======================== 122 | 123 | Before we dig into the core aspects of the package, you will quickly notice 124 | that many of the methods and functions defined here return a custom object 125 | class. This return class is defined in :py:class:`biosppy.utils.ReturnTuple`. 126 | The goal of this return class is to strengthen the semantic relationship 127 | between a function's output variables, their names, and what is described in 128 | the documentation. Consider the following function definition: 129 | 130 | .. code:: python 131 | 132 | def compute(a, b): 133 | """Simultaneously compute the sum, subtraction, multiplication and 134 | division between two integers. 135 | 136 | Args: 137 | a (int): First input integer. 138 | b (int): Second input integer. 139 | 140 | Returns: 141 | (tuple): containing: 142 | sum (int): Sum (a + b). 143 | sub (int): Subtraction (a - b). 144 | mult (int): Multiplication (a * b). 145 | div (int): Integer division (a / b). 146 | 147 | """ 148 | 149 | if b == 0: 150 | raise ValueError("Input 'b' cannot be zero.") 151 | 152 | v1 = a + b 153 | v2 = a - b 154 | v3 = a * b 155 | v4 = a / b 156 | 157 | return v1, v2, v3, v4 158 | 159 | Note that Python doesn't actually support returning multiple objects. In this 160 | case, the ``return`` statement packs the objects into a tuple. 161 | 162 | .. code:: python 163 | 164 | >>> out = compute(4, 50) 165 | >>> type(out) 166 | 167 | >>> print out 168 | (54, -46, 200, 0) 169 | 170 | This is pretty straightforward, yet it shows one disadvantage of the native 171 | Python return pattern: the semantics of the output elements (i.e. what each 172 | variable actually represents) are only implicitly defined with the ordering 173 | of the docstring. If there isn't a dosctring available (yikes!), the only way 174 | to figure out the meaning of the output is by analyzing the code itself. 175 | 176 | This is not necessarily a bad thing. One should always try to understand, 177 | at least in broad terms, how any given function works. However, the initial 178 | steps of the data analysis process encompass a lot of experimentation and 179 | interactive exploration of the data. This is important in order to have an 180 | initial sense of the quality of the data and what information we may be able to 181 | extract. In this case, the user typically already knows what a function does, 182 | but it is cumbersome to remember by heart the order of the outputs, without 183 | having to constantly check out the documentation. 184 | 185 | For instance, does the `numpy.histogram 186 | `__ 187 | function first return the edges or the values of the histogram? Maybe it's the 188 | edges first, which correspond to the x axis. Oops, it's actually the other way 189 | around... 190 | 191 | In this case, it could be useful to have an explicit reference directly in the 192 | return object to what each variable represents. Returning to the example above, 193 | we would like to have something like: 194 | 195 | .. code:: python 196 | 197 | >>> out = compute(4, 50) 198 | >>> print out 199 | (sum=54, sub=-46, mult=200, div=0) 200 | 201 | This is exactly what :py:class:`biosppy.utils.ReturnTuple` accomplishes. 202 | Rewriting the `compute` function to work with `ReturnTuple` is simple. Just 203 | construct the return object with a tuple of strings with names for each output 204 | variable: 205 | 206 | .. code:: python 207 | 208 | from biosppy import utils 209 | 210 | def compute_new(a, b): 211 | """Simultaneously compute the sum, subtraction, multiplication and 212 | division between two integers. 213 | 214 | Args: 215 | a (int): First input integer. 216 | b (int): Second input integer. 217 | 218 | Returns: 219 | (ReturnTuple): containing: 220 | sum (int): Sum (a + b). 221 | sub (int): Subtraction (a - b). 222 | mult (int): Multiplication (a * b). 223 | div (int): Integer division (a / b). 224 | 225 | """ 226 | 227 | if b == 0: 228 | raise ValueError("Input 'b' cannot be zero.") 229 | 230 | v1 = a + b 231 | v2 = a - b 232 | v3 = a * b 233 | v4 = a / b 234 | 235 | # build the return object 236 | output = utils.ReturnTuple((v1, v2, v3, v4), ('sum', 'sub', 'mult', 'div')) 237 | 238 | return output 239 | 240 | The output now becomes: 241 | 242 | .. code:: python 243 | 244 | >>> out = compute_new(4, 50) 245 | >>> print out 246 | ReturnTuple(sum=54, sub=-46, mult=200, div=0) 247 | 248 | It allows to access a specific variable by key, like a dictionary: 249 | 250 | .. code:: python 251 | 252 | >>> out['sum'] 253 | 54 254 | 255 | And to list all the available keys: 256 | 257 | .. code:: python 258 | 259 | >>> out.keys() 260 | ['sum', 'sub', 'mult', 'div'] 261 | 262 | It is also possible to convert the object to a more traditional dictionary, 263 | specifically an `OrderedDict `__: 264 | 265 | .. code:: python 266 | 267 | >>> d = out.as_dict() 268 | >>> print d 269 | OrderedDict([('sum', 54), ('sub', -46), ('mult', 200), ('div', 0)]) 270 | 271 | Dictionary-like unpacking is supported: 272 | 273 | .. code:: python 274 | 275 | >>> some_function(**out) 276 | 277 | `ReturnTuple` is heavily inspired by `namedtuple `__, 278 | but without the dynamic class generation at object creation. It is a subclass 279 | of `tuple`, therefore it maintains compatibility with the native return pattern. 280 | It is still possible to unpack the variables in the usual way: 281 | 282 | .. code:: python 283 | 284 | >>> a, b, c, d = compute_new(4, 50) 285 | >>> print a, b, c, d 286 | 54 -46 200 0 287 | 288 | The behavior is slightly different when only one variable is returned. In this 289 | case it is necessary to explicitly unpack a one-element tuple: 290 | 291 | .. code:: python 292 | 293 | from biosppy import utils 294 | 295 | def foo(): 296 | """Returns 'bar'.""" 297 | 298 | out = 'bar' 299 | 300 | return utils.ReturnTuple((out, ), ('out', )) 301 | 302 | .. code:: python 303 | 304 | >>> out, = foo() 305 | >>> print out 306 | 'bar' 307 | 308 | A First Approach 309 | ================ 310 | 311 | One of the major goals of `biosppy` is to provide an easy starting point into 312 | the world of biosignal processing. For that reason, we provide simple turnkey 313 | solutions for each of the supported biosignal types. These functions implement 314 | typical methods to filter, transform, and extract signal features. Let's see 315 | how this works for the example of the ECG signal. 316 | 317 | The GitHub repository includes a few example signals (see 318 | `here `__). To load 319 | and plot the raw ECG signal follow: 320 | 321 | .. code:: python 322 | 323 | >>> import numpy as np 324 | >>> import pylab as pl 325 | >>> from biosppy import storage 326 | >>> 327 | >>> signal, mdata = storage.load_txt('.../examples/ecg.txt') 328 | >>> Fs = mdata['sampling_rate'] 329 | >>> N = len(signal) # number of samples 330 | >>> T = (N - 1) / Fs # duration 331 | >>> ts = np.linspace(0, T, N, endpoint=False) # relative timestamps 332 | >>> pl.plot(ts, signal, lw=2) 333 | >>> pl.grid() 334 | >>> pl.show() 335 | 336 | This should produce a similar output to the one shown below. 337 | 338 | .. image:: images/ECG_raw.png 339 | :align: center 340 | :width: 80% 341 | :alt: Example of a raw ECG signal. 342 | 343 | This signal is a Lead I ECG signal acquired at 1000 Hz, with a resolution of 12 344 | bit. Although of good quality, it exhibits powerline noise interference, has a 345 | DC offset resulting from the acquisition device, and we can also observe the 346 | influence of breathing in the variability of R-peak amplitudes. 347 | 348 | We can minimize the effects of these artifacts and extract a bunch of features 349 | with the :py:class:`biosppy.signals.ecg.ecg` function: 350 | 351 | .. code:: python 352 | 353 | >>> from biosppy.signals import ecg 354 | >>> out = ecg.ecg(signal=signal, sampling_rate=Fs, show=True) 355 | 356 | It should produce a plot like the one below. 357 | 358 | .. image:: images/ECG_summary.png 359 | :align: center 360 | :width: 80% 361 | :alt: Example of processed ECG signal. 362 | 363 | 364 | 365 | 366 | Signal Processing 367 | ================= 368 | 369 | To do.. 370 | 371 | Clustering 372 | ========== 373 | 374 | To do.. 375 | 376 | Biometrics 377 | ========== 378 | 379 | To do.. 380 | 381 | What's Next? 382 | ============ 383 | 384 | To do.. 385 | 386 | References 387 | ========== 388 | 389 | To do. 390 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from biosppy import storage 5 | 6 | import warnings 7 | 8 | from biosppy.signals import ecg 9 | from biosppy.signals.acc import acc 10 | 11 | warnings.simplefilter(action='ignore', category=FutureWarning) 12 | 13 | # load raw ECG and ACC signals 14 | ecg_signal, _ = storage.load_txt('./examples/ecg.txt') 15 | acc_signal, _ = storage.load_txt('./examples/acc.txt') 16 | 17 | 18 | # Setting current path 19 | current_dir = os.path.dirname(sys.argv[0]) 20 | ecg_plot_path = os.path.join(current_dir, 'ecg.png') 21 | acc_plot_path = os.path.join(current_dir, 'acc.png') 22 | 23 | # Process it and plot. Set interactive=True to display an interactive window 24 | out_ecg = ecg.ecg(signal=ecg_signal, sampling_rate=1000., path=ecg_plot_path, interactive=True) 25 | out_acc = acc(signal=acc_signal, sampling_rate=1000., path=acc_plot_path, interactive=True) 26 | 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bidict==0.13.1 2 | h5py==2.7.1 3 | matplotlib==2.1.2 4 | numpy==1.22.0 5 | scikit-learn==0.19.1 6 | scipy==1.2.0 7 | shortuuid==0.5.0 8 | six==1.11.0 9 | joblib==0.11 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | release = egg_info -RDb '' 3 | 4 | [wheel] 5 | universal = 1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | BioSPPy 5 | ------- 6 | 7 | A toolbox for biosignal processing written in Python. 8 | 9 | :copyright: (c) 2015-2018 by Instituto de Telecomunicacoes 10 | :license: BSD 3-clause, see LICENSE for more details. 11 | """ 12 | 13 | # Note: To use the 'upload' functionality of this file, you must: 14 | # $ pip install twine 15 | 16 | import io 17 | import os 18 | import sys 19 | from shutil import rmtree 20 | 21 | from setuptools import find_packages, setup, Command 22 | 23 | # Package meta-data. 24 | NAME = 'biosppy' 25 | DESCRIPTION = 'A toolbox for biosignal processing written in Python.' 26 | URL = 'https://github.com/PIA-Group/BioSPPy' 27 | EMAIL = 'carlos.carreiras@lx.it.pt' 28 | AUTHOR = 'Instituto de Telecomunicacoes' 29 | REQUIRES_PYTHON = '>=2.7.10' 30 | VERSION = None 31 | LICENSE = 'BSD 3-clause' 32 | 33 | # What packages are required for this module to be executed? 34 | REQUIRED = [ 35 | 'bidict', 36 | 'h5py', 37 | 'matplotlib', 38 | 'numpy', 39 | 'scikit-learn', 40 | 'scipy', 41 | 'shortuuid', 42 | 'six', 43 | 'joblib', 44 | 'opencv-python' 45 | ] 46 | 47 | # What packages are optional? 48 | EXTRAS = { 49 | # 'fancy feature': ['django'], 50 | } 51 | 52 | # The rest you shouldn't have to touch too much :) 53 | # ------------------------------------------------ 54 | # Except, perhaps the License and Trove Classifiers! 55 | # If you do change the License, remember to change the Trove Classifier for that! 56 | 57 | here = os.path.abspath(os.path.dirname(__file__)) 58 | 59 | # Import the README and use it as the long-description. 60 | # Note: this will only work if 'README.md' is present in your MANIFEST.in file! 61 | try: 62 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 63 | long_description = '\n' + f.read() 64 | except FileNotFoundError: 65 | long_description = DESCRIPTION 66 | 67 | # Load the package's __version__.py module as a dictionary. 68 | about = {} 69 | if not VERSION: 70 | with open(os.path.join(here, NAME, '__version__.py')) as f: 71 | exec(f.read(), about) 72 | else: 73 | about['__version__'] = VERSION 74 | 75 | 76 | class UploadCommand(Command): 77 | """Support setup.py upload.""" 78 | 79 | description = 'Build and publish the package.' 80 | user_options = [] 81 | 82 | @staticmethod 83 | def status(s): 84 | """Prints things in bold.""" 85 | print('\033[1m{0}\033[0m'.format(s)) 86 | 87 | def initialize_options(self): 88 | pass 89 | 90 | def finalize_options(self): 91 | pass 92 | 93 | def run(self): 94 | try: 95 | self.status('Removing previous builds…') 96 | rmtree(os.path.join(here, 'dist')) 97 | except OSError: 98 | pass 99 | 100 | self.status('Building Source and Wheel (universal) distribution…') 101 | os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 102 | 103 | self.status('Uploading the package to PyPI via Twine…') 104 | os.system('twine upload dist/*') 105 | 106 | # self.status('Pushing git tags…') 107 | # os.system('git tag v{0}'.format(about['__version__'])) 108 | # os.system('git push --tags') 109 | 110 | sys.exit() 111 | 112 | 113 | # Where the magic happens: 114 | setup( 115 | name=NAME, 116 | version=about['__version__'], 117 | description=DESCRIPTION, 118 | long_description=long_description, 119 | long_description_content_type='text/markdown', 120 | author=AUTHOR, 121 | author_email=EMAIL, 122 | python_requires=REQUIRES_PYTHON, 123 | url=URL, 124 | packages=find_packages(exclude=('tests',)), 125 | # If your package is a single module, use this instead of 'packages': 126 | # py_modules=['mypackage'], 127 | 128 | # entry_points={ 129 | # 'console_scripts': ['mycli=mymodule:cli'], 130 | # }, 131 | install_requires=REQUIRED, 132 | extras_require=EXTRAS, 133 | include_package_data=True, 134 | license=LICENSE, 135 | classifiers=[ 136 | # Trove classifiers 137 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 138 | 'Intended Audience :: Developers', 139 | 'Intended Audience :: Education', 140 | 'Intended Audience :: Science/Research', 141 | 'Natural Language :: English', 142 | 'License :: OSI Approved :: BSD License', 143 | 'Programming Language :: Python', 144 | 'Programming Language :: Python :: 2', 145 | 'Programming Language :: Python :: 2.7', 146 | 'Programming Language :: Python :: 3', 147 | 'Programming Language :: Python :: 3.5', 148 | 'Programming Language :: Python :: 3.6', 149 | 'Programming Language :: Python :: Implementation :: CPython', 150 | 'Programming Language :: Python :: Implementation :: PyPy', 151 | 'Operating System :: OS Independent', 152 | ], 153 | # $ setup.py publish support. 154 | cmdclass={ 155 | 'upload': UploadCommand, 156 | }, 157 | ) 158 | --------------------------------------------------------------------------------