├── docs ├── .nojekyll ├── objects.inv ├── _static │ ├── file.png │ ├── minus.png │ ├── plus.png │ ├── favicon.png │ ├── fonts │ │ ├── Lato-Bold.ttf │ │ ├── Inconsolata.ttf │ │ ├── Lato-Regular.ttf │ │ ├── Lato │ │ │ ├── lato-bold.eot │ │ │ ├── lato-bold.ttf │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-italic.eot │ │ │ ├── lato-italic.ttf │ │ │ ├── lato-italic.woff │ │ │ ├── lato-regular.eot │ │ │ ├── lato-regular.ttf │ │ │ ├── lato-bolditalic.eot │ │ │ ├── lato-bolditalic.ttf │ │ │ ├── lato-italic.woff2 │ │ │ ├── lato-regular.woff │ │ │ ├── lato-regular.woff2 │ │ │ ├── lato-bolditalic.woff │ │ │ └── lato-bolditalic.woff2 │ │ ├── RobotoSlab-Bold.ttf │ │ ├── Inconsolata-Bold.ttf │ │ ├── Inconsolata-Regular.ttf │ │ ├── RobotoSlab-Regular.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ └── RobotoSlab │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ └── roboto-slab-v7-regular.woff2 │ ├── css │ │ ├── fonts │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-normal.woff │ │ │ ├── lato-normal.woff2 │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ ├── lato-bold-italic.woff │ │ │ ├── lato-bold-italic.woff2 │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── lato-normal-italic.woff │ │ │ └── lato-normal-italic.woff2 │ │ └── badge_only.css │ ├── devicely-logo-white.png │ ├── documentation_options.js │ ├── js │ │ ├── badge_only.js │ │ ├── html5shiv.min.js │ │ ├── html5shiv-printshiv.min.js │ │ └── theme.js │ ├── pygments.css │ └── doctools.js ├── _images │ └── devicely_structure.png ├── _sources │ ├── moduleref.rst.txt │ ├── index.rst.txt │ └── contribution.rst.txt ├── search.html ├── py-modindex.html └── index.html ├── sphinx ├── favicon.png ├── devicely-logo.png ├── devicely-logo-white.png ├── devicely_structure.png ├── Makefile ├── moduleref.rst ├── make.bat ├── README.md ├── conf.py ├── index.rst └── contribution.rst ├── imgs └── logo │ └── devicely-logo.png ├── inst ├── devicely_structure.pdf ├── devicely_structure.png ├── devicely_structure.tex ├── paper.bib └── paper.md ├── tests ├── Empatica_test_data │ └── test_data_read │ │ ├── tags.csv │ │ ├── HR.csv │ │ ├── IBI.csv │ │ ├── TEMP.csv │ │ ├── info.txt │ │ └── EDA.csv ├── Faros_test_data │ ├── testfile_read.EDF │ └── testdir_read │ │ ├── meta.json │ │ ├── Marker.csv │ │ └── HRV.csv ├── Timestamp_test_data │ └── tags.csv ├── SpaceLabs_test_data │ ├── spacelabs_repeated.abp │ └── spacelabs.abp ├── Everion_test_data │ ├── CsvData_analytics_events_EV-EC39-7079-57A8.csv │ ├── CsvData_sensor_data_EV-EC39-7079-57A8.csv │ └── CsvData_attributes_dailys_EV-EC39-7079-57A8.csv ├── test_timestamp.py ├── test_muse.py ├── test_shimmer.py └── test_spacelabs.py ├── devicely ├── __init__.py ├── muse.py ├── time_stamp.py ├── shimmer_plus.py ├── spacelabs.py └── faros.py ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── pypi.yml │ ├── docs.yml │ └── test.yml ├── LICENSE ├── pyproject.toml ├── CHANGELOG.md ├── .gitignore └── README.md /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/objects.inv -------------------------------------------------------------------------------- /sphinx/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/sphinx/favicon.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/favicon.png -------------------------------------------------------------------------------- /sphinx/devicely-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/sphinx/devicely-logo.png -------------------------------------------------------------------------------- /imgs/logo/devicely-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/imgs/logo/devicely-logo.png -------------------------------------------------------------------------------- /inst/devicely_structure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/inst/devicely_structure.pdf -------------------------------------------------------------------------------- /inst/devicely_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/inst/devicely_structure.png -------------------------------------------------------------------------------- /sphinx/devicely-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/sphinx/devicely-logo-white.png -------------------------------------------------------------------------------- /sphinx/devicely_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/sphinx/devicely_structure.png -------------------------------------------------------------------------------- /docs/_static/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /docs/_images/devicely_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_images/devicely_structure.png -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Inconsolata.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /tests/Empatica_test_data/test_data_read/tags.csv: -------------------------------------------------------------------------------- 1 | 1551453311.68 2 | 1551453318.17 3 | 1551453319.49 4 | 1551453328.04 5 | -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/devicely-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/devicely-logo-white.png -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab-Bold.ttf -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Inconsolata-Bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /tests/Faros_test_data/testfile_read.EDF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/tests/Faros_test_data/testfile_read.EDF -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab-Regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpi-dhc/devicely/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /tests/Faros_test_data/testdir_read/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "start_time" : "2018-10-12 16:54:12", 3 | "sample_freqs" : { 4 | "ECG" : 500.0, 5 | "ACC" : 25.0, 6 | "Marker" : 1.0, 7 | "HRV" : 5.0 8 | }, 9 | "units" : { 10 | "ECG" : "uV", 11 | "ACC" : "mg", 12 | "HRV" : "ms" 13 | } 14 | } -------------------------------------------------------------------------------- /tests/Empatica_test_data/test_data_read/HR.csv: -------------------------------------------------------------------------------- 1 | 1551453311.000000 2 | 1.000000 3 | 88.00 4 | 88.00 5 | 88.67 6 | 85.00 7 | 89.80 8 | 88.50 9 | 93.57 10 | 97.88 11 | 100.33 12 | 102.30 13 | 104.36 14 | 106.25 15 | 107.38 16 | 108.29 17 | 109.27 18 | 110.62 19 | 110.76 20 | 111.00 21 | 110.32 22 | 109.50 23 | 108.29 24 | 107.14 25 | 105.65 26 | 104.29 27 | 103.12 28 | 102.00 29 | 101.11 30 | 100.46 31 | -------------------------------------------------------------------------------- /docs/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1.1.1', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false 12 | }; -------------------------------------------------------------------------------- /devicely/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Import all readers. 3 | """ 4 | from .empatica import EmpaticaReader 5 | from .everion import EverionReader 6 | from .faros import FarosReader 7 | from .time_stamp import TimeStampReader 8 | from .spacelabs import SpacelabsReader 9 | from .shimmer_plus import ShimmerPlusReader 10 | from .muse import MuseReader 11 | 12 | try: 13 | import importlib.metadata as importlib_metadata 14 | except ModuleNotFoundError: 15 | import importlib_metadata 16 | 17 | __version__ = importlib_metadata.version(__name__) 18 | 19 | -------------------------------------------------------------------------------- /tests/Timestamp_test_data/tags.csv: -------------------------------------------------------------------------------- 1 | 1,2019/3/1(fri) 16:16:37,Shake 2 | 2,2019/3/1(fri) 16:17:43,Start 3 | 3,2019/3/1(fri) 16:18:20,BP Measurement 4 | 4,2019/3/1(fri) 16:19:51,BP Measurement 5 | 5,2019/3/1(fri) 16:22:00,BP Measurement 6 | 6,2019/3/1(fri) 16:23:34,BP Measurement 7 | 7,2019/3/1(fri) 16:25:07,BP Measurement 8 | 8,2019/3/1(fri) 16:26:14,Stress Test Start 9 | 8,2019/3/1(fri) 16:31:00,Stress Test End 10 | 12,2019/3/1(fri) 16:31:56,BP Measurement 11 | 13,2019/3/1(fri) 16:33:39,BP Measurement 12 | 14,2019/3/1(fri) 16:35:16,BP Measurement 13 | 16,2019/3/1(fri) 16:37:00,BP Measurement 14 | 17,2019/3/1(fri) 16:38:39,BP Measurement 15 | 18,2019/3/1(fri) 16:38:48,End 16 | 19,2019/3/1(fri) 16:39:32,Shake 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /sphinx/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /tests/Faros_test_data/testdir_read/Marker.csv: -------------------------------------------------------------------------------- 1 | Marker 2 | 0.0 3 | 0.0 4 | 0.0 5 | 0.0 6 | 0.0 7 | 0.0 8 | 0.0 9 | 0.0 10 | 0.0 11 | 0.0 12 | 0.0 13 | 0.0 14 | 0.0 15 | 0.0 16 | 0.0 17 | 0.0 18 | 0.0 19 | 0.0 20 | 0.0 21 | 0.0 22 | 0.0 23 | 0.0 24 | 0.0 25 | 0.0 26 | 0.0 27 | 0.0 28 | 0.0 29 | 0.0 30 | 0.0 31 | 0.0 32 | 0.0 33 | 0.0 34 | 0.0 35 | 0.0 36 | 0.0 37 | 0.0 38 | 0.0 39 | 0.0 40 | 0.0 41 | 0.0 42 | 0.0 43 | 0.0 44 | 0.0 45 | 0.0 46 | 0.0 47 | 0.0 48 | 0.0 49 | 0.0 50 | 0.0 51 | 0.0 52 | 0.0 53 | 0.0 54 | 0.0 55 | 0.0 56 | 0.0 57 | 0.0 58 | 0.0 59 | 0.0 60 | 0.0 61 | 0.0 62 | 0.0 63 | 0.0 64 | 0.0 65 | 0.0 66 | 0.0 67 | 0.0 68 | 0.0 69 | 0.0 70 | 0.0 71 | 0.0 72 | 0.0 73 | 0.0 74 | 1.0 75 | 0.0 76 | 0.0 77 | 0.0 78 | 0.0 79 | 0.0 80 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | name: "Publish to PyPi" 2 | on: 3 | release: 4 | types: [published] 5 | branches: 6 | - main 7 | jobs: 8 | publish-dists: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.9 17 | 18 | - name: Install Poetry 19 | uses: snok/install-poetry@v1 20 | 21 | - name: Build artifacts 22 | run: | 23 | poetry install 24 | poetry build 25 | 26 | - name: Publish dists 27 | uses: pypa/gh-action-pypi-publish@release/v1 28 | with: 29 | user: __token__ 30 | password: ${{ secrets.PYPI_API_TOKEN }} 31 | 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Linux] 28 | - Python [e.g. 3.7, 3.8] 29 | - Version [e.g. 1.1.1] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /sphinx/moduleref.rst: -------------------------------------------------------------------------------- 1 | Module reference 2 | ================ 3 | 4 | devicely.EmpaticaReader 5 | ----------------------- 6 | 7 | .. automodule:: devicely.empatica 8 | :members: 9 | 10 | devicely.SpacelabsReader 11 | ------------------------ 12 | 13 | .. automodule:: devicely.spacelabs 14 | :members: 15 | 16 | devicely.FarosReader 17 | -------------------- 18 | 19 | .. automodule:: devicely.faros 20 | :members: 21 | 22 | devicely.EverionReader 23 | ---------------------- 24 | 25 | .. automodule:: devicely.everion 26 | :members: 27 | 28 | devicely.ShimmerReader 29 | ---------------------- 30 | 31 | .. automodule:: devicely.shimmer_plus 32 | :members: 33 | 34 | devicely.MuseReader 35 | ------------------------ 36 | 37 | .. automodule:: devicely.muse 38 | :members: 39 | 40 | devicely.TimeStampReader 41 | ------------------------ 42 | 43 | .. automodule:: devicely.time_stamp 44 | :members: 45 | -------------------------------------------------------------------------------- /docs/_sources/moduleref.rst.txt: -------------------------------------------------------------------------------- 1 | Module reference 2 | ================ 3 | 4 | devicely.EmpaticaReader 5 | ----------------------- 6 | 7 | .. automodule:: devicely.empatica 8 | :members: 9 | 10 | devicely.SpacelabsReader 11 | ------------------------ 12 | 13 | .. automodule:: devicely.spacelabs 14 | :members: 15 | 16 | devicely.FarosReader 17 | -------------------- 18 | 19 | .. automodule:: devicely.faros 20 | :members: 21 | 22 | devicely.EverionReader 23 | ---------------------- 24 | 25 | .. automodule:: devicely.everion 26 | :members: 27 | 28 | devicely.ShimmerReader 29 | ---------------------- 30 | 31 | .. automodule:: devicely.shimmer_plus 32 | :members: 33 | 34 | devicely.MuseReader 35 | ------------------------ 36 | 37 | .. automodule:: devicely.muse 38 | :members: 39 | 40 | devicely.TimeStampReader 41 | ------------------------ 42 | 43 | .. automodule:: devicely.time_stamp 44 | :members: 45 | -------------------------------------------------------------------------------- /docs/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /sphinx/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /tests/SpaceLabs_test_data/spacelabs_repeated.abp: -------------------------------------------------------------------------------- 1 | 2 | 000002 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 01.01.1999 19 | 20 | 21 | 22 | 23 | 24 | 25 | Unknown Line 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 15 52 | 17,05,11,0,0,0,"EB","" 53 | 17,05,142,118,99,61,"","" 54 | 17,07,152,112,95,61,"","" 55 | 17,09,151,115,96,61,"","" 56 | 17,11,145,110,91,59,"","" 57 | 17,13,3,0,0,0,"EB","" 58 | 17,25,4,0,0,0,"EB","" 59 | 17,28,164,119,95,63,"","" 60 | 17,31,154,116,95,63,"","" 61 | 17,34,149,119,98,63,"","" 62 | 17,36,153,118,96,60,"","" 63 | 17,39,148,114,93,62,"","" 64 | 23,42,148,114,93,62,"","" 65 | 23,59,148,114,93,62,"","" 66 | 00,01,148,114,93,62,"","" 67 | 16.09.1966native americanDr. Hannibal LecteradminNOTCONFIRMED0 -------------------------------------------------------------------------------- /tests/SpaceLabs_test_data/spacelabs.abp: -------------------------------------------------------------------------------- 1 | 2 | 000002 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 01.01.1999 19 | 20 | 21 | 22 | 23 | 24 | 25 | Unknown Line 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 15 52 | 17,03,11,0,0,0,"EB","" 53 | 17,05,142,118,99,61,"","" 54 | 17,07,152,112,95,61,"","" 55 | 17,09,151,115,96,61,"","" 56 | 17,11,145,110,91,59,"","" 57 | 17,13,3,0,0,0,"EB","" 58 | 17,25,4,0,0,0,"EB","" 59 | 17,28,164,119,95,63,"","" 60 | 17,31,154,116,95,63,"","" 61 | 17,34,149,119,98,63,"","" 62 | 17,36,153,118,96,60,"","" 63 | 17,39,148,114,93,62,"","" 64 | 23,42,148,114,93,62,"","" 65 | 23,59,148,114,93,62,"","" 66 | 00,01,148,114,93,62,"","" 67 | 16.09.1966native americanDr. Hannibal LecteradminNOTCONFIRMED0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Digital Health Center (Hasso Plattner Institute) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: "Sphinx Build" 2 | on: 3 | release: 4 | types: [published] 5 | branches: [main] 6 | 7 | jobs: 8 | docs: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | token: ${{ secrets.PAT }} 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: 3.9 19 | 20 | - name: Install Poetry 21 | uses: snok/install-poetry@v1 22 | 23 | - name: Install dependencies 24 | run: | 25 | poetry install 26 | sudo apt-get install pandoc 27 | 28 | - name: Build documentation 29 | run: | 30 | cd sphinx 31 | poetry run make clean 32 | poetry run make html 33 | cd .. 34 | rm -rf docs 35 | mv sphinx/_build/html docs 36 | touch docs/.nojekyll 37 | ls -la docs 38 | 39 | - name: Push built docs 40 | run: | 41 | git config --global user.name 'jostmorgenstern' 42 | git config --global user.email 'jostmorgenstern@users.noreply.github.com' 43 | git add . 44 | git commit -am "Automated docs generation" 45 | git status 46 | git push origin main 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/Empatica_test_data/test_data_read/IBI.csv: -------------------------------------------------------------------------------- 1 | 1551453301.000000, IBI 2 | 145.631666,0.62509 3 | 146.522332,0.890666 4 | 151.725695,1.062549 5 | 152.835121,1.109426 6 | 153.710161,0.875040 7 | 154.725832,1.015671 8 | 161.679276,1.031297 9 | 162.741824,1.062549 10 | 163.773122,1.031297 11 | 164.757542,0.984420 12 | 165.757587,1.000046 13 | 166.695130,0.937543 14 | 167.585796,0.890666 15 | 168.429585,0.843789 16 | 169.288999,0.859414 17 | 170.148413,0.859414 18 | 170.929699,0.781286 19 | 171.695359,0.765660 20 | 172.539148,0.843789 21 | 173.492316,0.953169 22 | 174.398608,0.906291 23 | 175.320525,0.921917 24 | 176.258068,0.937543 25 | 177.258114,1.000046 26 | 178.148780,0.890666 27 | 179.086323,0.937543 28 | 180.023865,0.937543 29 | 180.961408,0.937543 30 | 181.867700,0.906291 31 | 182.898997,1.031297 32 | 183.680283,0.781286 33 | 184.555323,0.875040 34 | 185.539743,0.984420 35 | 186.508537,0.968794 36 | 187.555460,1.046923 37 | 188.508629,0.953169 38 | 189.477423,0.968794 39 | 190.477469,1.000046 40 | 191.446263,0.968794 41 | 192.352555,0.906291 42 | 193.305723,0.953169 43 | 194.274518,0.968794 44 | 195.165184,0.890666 45 | 196.071475,0.906291 46 | 196.977767,0.906291 47 | 197.868432,0.890666 48 | 198.634092,0.765660 49 | 199.446630,0.812537 50 | 200.306044,0.859414 51 | -------------------------------------------------------------------------------- /tests/Empatica_test_data/test_data_read/TEMP.csv: -------------------------------------------------------------------------------- 1 | 1551453301.000000 2 | 4.000000 3 | 23.75 4 | 23.75 5 | 23.75 6 | 23.75 7 | 23.75 8 | 23.75 9 | 23.75 10 | 23.75 11 | 23.71 12 | 23.71 13 | 23.71 14 | 23.71 15 | 23.69 16 | 23.69 17 | 23.69 18 | 23.69 19 | 23.65 20 | 23.65 21 | 23.65 22 | 23.65 23 | 23.61 24 | 23.61 25 | 23.61 26 | 23.61 27 | 23.57 28 | 23.57 29 | 23.57 30 | 23.57 31 | 23.55 32 | 23.55 33 | 23.55 34 | 23.55 35 | 23.55 36 | 23.55 37 | 23.55 38 | 23.55 39 | 23.49 40 | 23.49 41 | 23.49 42 | 23.49 43 | 23.47 44 | 23.47 45 | 23.47 46 | 23.47 47 | 23.45 48 | 23.45 49 | 23.45 50 | 23.45 51 | 23.43 52 | 23.43 53 | 23.43 54 | 23.43 55 | 23.39 56 | 23.39 57 | 23.39 58 | 23.39 59 | 23.35 60 | 23.35 61 | 23.35 62 | 23.35 63 | 23.37 64 | 23.37 65 | 23.37 66 | 23.37 67 | 23.31 68 | 23.31 69 | 23.31 70 | 23.31 71 | 23.29 72 | 23.29 73 | 23.29 74 | 23.29 75 | 23.27 76 | 23.27 77 | 23.27 78 | 23.27 79 | 23.23 80 | 23.23 81 | 23.23 82 | 23.23 83 | 23.23 84 | 23.23 85 | 23.23 86 | 23.23 87 | 23.23 88 | 23.23 89 | 23.23 90 | 23.23 91 | 23.17 92 | 23.17 93 | 23.17 94 | 23.17 95 | 23.17 96 | 23.17 97 | 23.17 98 | 23.17 99 | 23.17 100 | 23.17 101 | 23.17 102 | 23.17 103 | 23.13 104 | 23.13 105 | 23.13 106 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "devicely" 3 | version = "1.1.1" 4 | description = "Devicely: A Python package for reading, timeshifting and writing sensor data." 5 | authors = [ 6 | "Ariane Morassi Sasso ", 7 | "Jost Morgenstern ", 8 | "Felix Musmann ", 9 | "Bert Arnrich " 10 | ] 11 | license = "MIT" 12 | repository = "https://github.com/hpi-dhc/devicely" 13 | documentation = "https://hpi-dhc.github.io/devicely" 14 | classifiers=[ 15 | 'License :: OSI Approved :: MIT License', 16 | 'Programming Language :: Python', 17 | 'Programming Language :: Python :: 3.7', 18 | 'Programming Language :: Python :: 3.8', 19 | 'Programming Language :: Python :: 3.9' 20 | ] 21 | readme='README.md' 22 | 23 | [tool.poetry.dependencies] 24 | python = "^3.7.1" 25 | pandas = "^1.3.0" 26 | pyEDFlib = "^0.1.22" 27 | numpy = "^1.21.1" 28 | importlib-metadata = {version = "^1.0", python = "<3.8"} 29 | 30 | [tool.poetry.dev-dependencies] 31 | pytest = "^6.2.4" 32 | coverage = "^5.5" 33 | ipykernel = "^6.0.3" 34 | jupyter = "^1.0.0" 35 | nbsphinx = "^0.8.6" 36 | sphinx-rtd-theme = "^0.5.2" 37 | 38 | [build-system] 39 | requires = ["poetry-core>=1.0.0"] 40 | build-backend = "poetry.core.masonry.api" 41 | 42 | 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.1.0] - 2021-08-24 10 | - removed acceleration magnitude from devicely.EmpaticaReader and devicely.FarosReader since it was out of the scope of the package 11 | - added more flexibility to missing files (e.g. ACC.csv, EDA.csv) to devicely.EmpaticaReader 12 | - changed TagsReader to TimeStampReader to be more consistent with the class naming structure in devicely 13 | - deprecated methods in devicely.SpacelabsReader: set_window and drop_EB 14 | - fixed issue with the timestamp index and fixed column names in devicely.SpacelabsReader 15 | 16 | ## [1.0.0] - 2021-07-19 17 | ### Added 18 | - devicely.FarosReader can both read from and write to EDF files and directories 19 | - devicely.FarosReader has as attributes the individual dataframes (ACC, ECG, ...) and not only the joined dataframe 20 | 21 | ### Changed 22 | - in devicely.SpacelabsReader, use xml.etree from the standard library instead of third-party "xmltodict" 23 | - switch from setuptools to Poetry 24 | 25 | ### Removed 26 | - removed setup.py because static project files such as pyproject.toml are preferred 27 | -------------------------------------------------------------------------------- /sphinx/README.md: -------------------------------------------------------------------------------- 1 | # How to add documentation for a new module in devicely 2 | 3 | We use [sphinx](https://www.sphinx-doc.org/en/master/index.html), a tool to document Python packages based on the [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html) markup language. 4 | 5 | To get started, you need the packages **sphinx**, **sphinx_rtd_theme** and **nbsphinx**, all of which can be installed with pip. You also need to pip install devicely itself. 6 | 7 | The main file to work on is **index.rst**. Please add a short example use case for your newly written module to it. The existing examples should provide enough help. The example will be seen on the [main documentation page](https://hpi-dhc.github.io/devicely/index.html). 8 | Also you need to add docstrings to your module. The docstrings will be seen in the [module reference](https://hpi-dhc.github.io/devicely/index.html#module-reference). Please write one docstring for your class and one for each method that you want users to access. You can just copy the docstrings of existing modules and adjust them for your needs. Also add an *automodule* tag for your module to the bottom of **index.rst**. 9 | 10 | When you are done, run **make html**. You can ignore the warnings. This will create some files in _build/html. Move them to the **docs** directory in the root of the repo, but make sure the **docs** directory contains an empty file called **.nojekyll**. Then you should be able to see your docs online. Please run **make clean** before pushing. 11 | -------------------------------------------------------------------------------- /tests/Empatica_test_data/test_data_read/info.txt: -------------------------------------------------------------------------------- 1 | .csv files in this archive are in the following format: 2 | The first row is the initial time of the session expressed as unix timestamp in UTC. 3 | The second row is the sample rate expressed in Hz. 4 | 5 | TEMP.csv 6 | Data from temperature sensor expressed degrees on the Celsius (°C) scale. 7 | 8 | EDA.csv 9 | Data from the electrodermal activity sensor expressed as microsiemens (μS). 10 | 11 | BVP.csv 12 | Data from photoplethysmograph. 13 | 14 | ACC.csv 15 | Data from 3-axis accelerometer sensor. The accelerometer is configured to measure acceleration in the range [-2g, 2g]. Therefore the unit in this file is 1/64g. 16 | Data from x, y, and z axis are respectively in first, second, and third column. 17 | 18 | IBI.csv 19 | Time between individuals heart beats extracted from the BVP signal. 20 | No sample rate is needed for this file. 21 | The first column is the time (respect to the initial time) of the detected inter-beat interval expressed in seconds (s). 22 | The second column is the duration in seconds (s) of the detected inter-beat interval (i.e., the distance in seconds from the previous beat). 23 | 24 | HR.csv 25 | Average heart rate extracted from the BVP signal.The first row is the initial time of the session expressed as unix timestamp in UTC. 26 | The second row is the sample rate expressed in Hz. 27 | 28 | 29 | tags.csv 30 | Event mark times. 31 | Each row corresponds to a physical button press on the device; the same time as the status LED is first illuminated. 32 | The time is expressed as a unix timestamp in UTC and it is synchronized with initial time of the session indicated in the related data files from the corresponding session. 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing and coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: [3.7, 3.8, 3.9] 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | 25 | - name: Install Poetry 26 | uses: snok/install-poetry@v1 27 | 28 | - name: Install dependencies 29 | run: | 30 | poetry install 31 | 32 | - name: Test with pytest 33 | run: | 34 | poetry run python -m pytest 35 | 36 | - if: ${{ matrix.python-version == '3.9' }} 37 | name: Calculate coverage 38 | run: | 39 | poetry run coverage run --source=devicely -m pytest 40 | poetry run coverage xml 41 | COVERAGE=$(python -c "import xml.etree.ElementTree as ET; print(int(float(ET.parse('coverage.xml').getroot().attrib['line-rate']) * 100))") 42 | COLOR=$(echo $COVERAGE | python -c "import sys; from bisect import bisect; i=bisect([0,60,70,80,95,100], int(sys.stdin.read()))-1; print(['red', 'orange', 'yellow', 'yellowgreen', 'green', 'brightgreen'][i])") 43 | echo "COVERAGE=$COVERAGE" >> $GITHUB_ENV 44 | echo "COLOR=$COLOR" >> $GITHUB_ENV 45 | 46 | - if: ${{ matrix.python-version == '3.9' }} 47 | name: Create the coverage gist 48 | uses: schneegans/dynamic-badges-action@v1.0.0 49 | with: 50 | auth: ${{ secrets.GIST_SECRET }} 51 | gistID: 270a0114dfad9251945a146dd6d29fa6 52 | filename: devicely_coverage_main.json 53 | label: Test Coverage 54 | message: ${{ env.COVERAGE }}% 55 | color: ${{ env.COLOR }} 56 | -------------------------------------------------------------------------------- /tests/Empatica_test_data/test_data_read/EDA.csv: -------------------------------------------------------------------------------- 1 | 1551453301.000000 2 | 4.000000 3 | 0.000000 4 | 0.000000 5 | 0.005126 6 | 0.003844 7 | 0.003844 8 | 0.002563 9 | 0.000000 10 | 0.005126 11 | 0.005126 12 | 0.002563 13 | 0.002563 14 | 0.005126 15 | 0.003844 16 | 0.003844 17 | 0.002563 18 | 0.005126 19 | 0.003844 20 | 0.002563 21 | 0.002563 22 | 0.000000 23 | 0.001281 24 | 0.003844 25 | 0.002563 26 | 0.002563 27 | 0.005126 28 | 0.005126 29 | 0.002563 30 | 0.002563 31 | 0.003844 32 | 0.003844 33 | 0.003844 34 | 0.002563 35 | 0.001281 36 | 0.003844 37 | 0.003844 38 | 0.001281 39 | 0.001281 40 | 0.003844 41 | 0.003844 42 | 0.000000 43 | 0.001281 44 | 0.005126 45 | 0.002563 46 | 0.001281 47 | 0.001281 48 | 0.002563 49 | 0.002563 50 | 0.000000 51 | 0.000000 52 | 0.002563 53 | 0.000000 54 | 0.000000 55 | 0.000000 56 | 0.000000 57 | 0.000000 58 | 0.000000 59 | 0.000000 60 | 0.000000 61 | 0.000000 62 | 0.000000 63 | 0.000000 64 | 0.000000 65 | 0.000000 66 | 0.000000 67 | 0.000000 68 | 0.000000 69 | 0.000000 70 | 0.000000 71 | 0.000000 72 | 0.000000 73 | 0.000000 74 | 0.000000 75 | 0.000000 76 | 0.000000 77 | 0.000000 78 | 0.000000 79 | 0.000000 80 | 0.000000 81 | 0.000000 82 | 0.000000 83 | 0.000000 84 | 0.000000 85 | 0.000000 86 | 0.000000 87 | 0.000000 88 | 0.000000 89 | 0.000000 90 | 0.000000 91 | 0.000000 92 | 0.000000 93 | 0.000000 94 | 0.000000 95 | 0.000000 96 | 0.000000 97 | 0.000000 98 | 0.000000 99 | 0.000000 100 | 0.000000 101 | 0.000000 102 | 0.000000 103 | 0.000000 104 | 0.000000 105 | 0.000000 106 | 0.000000 107 | 0.000000 108 | 0.000000 109 | 0.000000 110 | 0.000000 111 | 0.000000 112 | 0.000000 113 | 0.000000 114 | 0.000000 115 | 0.000000 116 | 0.000000 117 | 0.000000 118 | 0.000000 119 | 0.000000 120 | 0.000000 121 | 0.000000 122 | 0.000000 123 | 0.000000 124 | 0.000000 125 | 0.000000 126 | 0.000000 127 | 0.000000 128 | 0.000000 129 | 0.000000 130 | 0.000000 131 | 0.000000 132 | 0.000000 133 | 0.000000 134 | 0.000000 135 | 0.000000 136 | -------------------------------------------------------------------------------- /tests/Everion_test_data/CsvData_analytics_events_EV-EC39-7079-57A8.csv: -------------------------------------------------------------------------------- 1 | count,streamType,tag,time,values 2 | 5622,7,1,1551458609,22.0 3 | 5621,7,1,1551458609,2.0 4 | 5620,7,1,1551458360,22.0 5 | 5619,7,1,1551458343,22.0 6 | 5618,7,1,1551458002,2.0 7 | 5617,7,1,1551457999,1.0 8 | 5616,7,1,1551454947,22.0 9 | 5615,7,1,1551454823,22.0 10 | 5614,7,1,1551454822,2.0 11 | 5613,7,1,1551454820,5.0 12 | 5612,7,1,1551454816,1.0 13 | 5611,7,1,1551454810,2.0 14 | 5600,7,1,1551453439,8.0 15 | 5599,7,1,1551453434,5.0 16 | 5598,7,1,1551453429,5.0 17 | 5597,7,1,1551453424,3.0 18 | 5596,7,1,1551453422,22.0 19 | 5595,7,1,1551453421,15.0 20 | 5594,7,1,1551453410,21.0 21 | 5593,7,1,1551453409,16.0 22 | 5592,7,16,1551453400,32.0 23 | 5591,7,1,1551453400,8.0 24 | 5590,7,1,1551453394,5.0 25 | 5589,7,1,1551453393,1.0 26 | 5588,7,1,1551453314,22.0 27 | 5587,7,1,1551448175,22.0 28 | 5586,7,1,1551448168,2.0 29 | 5610,7,1,1551454805,5.0 30 | 5609,7,1,1551454802,3.0 31 | 5608,7,1,1551454800,22.0 32 | 5607,7,1,1551454384,15.0 33 | 5606,7,1,1551454345,32.0 34 | 5605,7,1,1551453983,16.0 35 | 5604,7,1,1551453516,15.0 36 | 5603,7,1,1551453449,21.0 37 | 5602,7,1,1551453448,16.0 38 | 5601,7,16,1551453439,32.0 39 | 5585,7,1,1551448166,3.0 40 | 5584,7,16,1551448160,32.0 41 | 5583,7,1,1551448160,8.0 42 | 5582,7,1,1551448154,5.0 43 | 5581,7,1,1551448153,1.0 44 | 5580,7,1,1551448151,22.0 45 | 5579,7,1,1551448150,15.0 46 | 5578,7,1,1551448150,2.0 47 | 5570,7,1,1551447620,15.0 48 | 5569,7,1,1551447617,16.0 49 | 5568,7,1,1551447555,15.0 50 | 5567,7,1,1551447488,16.0 51 | 5566,7,1,1551447440,15.0 52 | 5565,7,1,1551447131,16.0 53 | 5564,7,1,1551446928,15.0 54 | 5563,7,1,1551446648,21.0 55 | 5562,7,1,1551446647,31.0 56 | 5561,7,1,1551446647,16.0 57 | 5560,7,16,1551446638,32.0 58 | 5559,7,1,1551446638,8.0 59 | 5558,7,1,1551446633,5.0 60 | 5557,7,1,1551446631,1.0 61 | 5556,7,1,1551446590,2.0 62 | 5577,7,1,1551448065,16.0 63 | 5576,7,1,1551448020,15.0 64 | 5575,7,1,1551448017,16.0 65 | 5574,7,1,1551448001,15.0 66 | 5573,7,1,1551447998,16.0 67 | 5572,7,1,1551447834,15.0 68 | 5571,7,1,1551447798,16.0 69 | 5555,7,1,1551446588,5.0 70 | 5554,7,1,1551446585,1.0 71 | 5553,7,1,1551446573,22.0 72 | 5552,7,1,1551446573,2.0 73 | -------------------------------------------------------------------------------- /devicely/muse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to process Muse data from the mind monitor application 3 | """ 4 | 5 | import os 6 | import random 7 | 8 | import numpy as np 9 | import pandas as pd 10 | 11 | 12 | class MuseReader: 13 | """ 14 | Parses, timeshifts and writes data generated by the Muse S headband using the Mind Monitor application. 15 | 16 | Attributes 17 | ---------- 18 | data : DataFrame 19 | dataframe of the read data 20 | """ 21 | 22 | def __init__(self, path): 23 | """ 24 | Parse the csv file located in the specified directory into a dataframe. 25 | 26 | Parameters 27 | ---------- 28 | path : str 29 | path to the csv file 30 | """ 31 | self.data = pd.read_csv(path, parse_dates=['TimeStamp'], index_col='TimeStamp') 32 | 33 | def write(self, path): 34 | """ 35 | Writes the dataframe back into a csv file. 36 | 37 | Parameters 38 | ---------- 39 | path : str 40 | path to the write file. 41 | """ 42 | 43 | self.data.reset_index().to_csv(path, index=False) 44 | 45 | def timeshift(self, shift='random'): 46 | """ 47 | Shifts the index column 'TimeStamp'. 48 | 49 | Parameters 50 | ---------- 51 | shift : None/'random', pd.Timestamp or pd.Timedelta 52 | If shift is not specified, shifts the data by a random time interval 53 | between one month and two years to the past. 54 | 55 | If shift is a timdelta, shifts the data by that timedelta. 56 | 57 | If shift is a timestamp, shifts the data such that the earliest entry 58 | is at that timestamp and the remaining values keep the same 59 | time distance to the first entry. 60 | """ 61 | 62 | if shift == 'random': 63 | one_month = pd.Timedelta('- 30 days').value 64 | two_years = pd.Timedelta('- 730 days').value 65 | random_timedelta = pd.Timedelta(random.uniform(one_month, two_years)) 66 | self.timeshift(random_timedelta) 67 | 68 | if isinstance(shift, pd.Timestamp): 69 | timedeltas = self.data.index - self.data.index[0] 70 | self.data.index = shift + timedeltas 71 | 72 | if isinstance(shift, pd.Timedelta): 73 | self.data.index += shift 74 | -------------------------------------------------------------------------------- /sphinx/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import sphinx_rtd_theme 14 | import sys 15 | import os 16 | 17 | # This step ensures that the source package is imported 18 | sys.path.insert(0, os.path.abspath("../")) 19 | 20 | import devicely 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = 'devicely' 25 | copyright = '2021, Digital Health Center (Hasso Plattner Institute)' 26 | author = 'Ariane Sasso, Jost Morgenstern, Felix Musmann, Bert Arnrich' 27 | 28 | # The full version, including alpha/beta/rc tags 29 | release = devicely.__version__ 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = ['sphinx.ext.autodoc', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.napoleon', 40 | 'nbsphinx', 41 | 'sphinx_rtd_theme'] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # List of patterns, relative to source directory, that match files and 47 | # directories to ignore when looking for source files. 48 | # This pattern also affects html_static_path and html_extra_path. 49 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 50 | 51 | 52 | # -- Options for HTML output ------------------------------------------------- 53 | 54 | # The theme to use for HTML and HTML Help pages. See the documentation for 55 | # a list of builtin themes. 56 | # 57 | html_theme = 'sphinx_rtd_theme' 58 | html_theme_options = { 59 | 'logo_only': True 60 | } 61 | # Add any paths that contain custom static files (such as style sheets) here, 62 | # relative to this directory. They are copied after the builtin static files, 63 | # so a file named "default.css" will overwrite the builtin "default.css". 64 | #html_static_path = ['_static'] 65 | 66 | html_logo = 'devicely-logo-white.png' 67 | html_favicon = 'favicon.png' 68 | -------------------------------------------------------------------------------- /tests/test_timestamp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the TimeStamp module 3 | """ 4 | import os 5 | import unittest 6 | 7 | import pandas as pd 8 | 9 | import devicely 10 | 11 | class TimeStampTestCase(unittest.TestCase): 12 | READ_PATH = os.path.join(os.getcwd(), 'tests/Timestamp_test_data/tags.csv') 13 | WRITE_PATH = os.path.join(os.getcwd(), 'tests/Timestamp_test_data/tags_written.csv') 14 | 15 | def setUp(self): 16 | tag_number = [1, 2, 3, 4, 5, 6, 7, 8, 8, 12, 13, 14, 16, 17, 18, 19] 17 | 18 | timestamps = pd.to_datetime(['2019-03-01 16:16:37', '2019-03-01 16:17:43', 19 | '2019-03-01 16:18:20', '2019-03-01 16:19:51', 20 | '2019-03-01 16:22:00', '2019-03-01 16:23:34', 21 | '2019-03-01 16:25:07', '2019-03-01 16:26:14', 22 | '2019-03-01 16:31:00', '2019-03-01 16:31:56', 23 | '2019-03-01 16:33:39', '2019-03-01 16:35:16', 24 | '2019-03-01 16:37:00', '2019-03-01 16:38:39', 25 | '2019-03-01 16:38:48', '2019-03-01 16:39:32']) 26 | 27 | tags = ['Shake', 'Start', 'BP Measurement', 'BP Measurement', 'BP Measurement', 'BP Measurement', 28 | 'BP Measurement', 'Stress Test Start', 'Stress Test End', 'BP Measurement', 'BP Measurement', 29 | 'BP Measurement', 'BP Measurement', 'BP Measurement', 'End', 'Shake'] 30 | 31 | self.data = pd.DataFrame({'tag_number': tag_number, 'time': timestamps, 'tag': tags}).set_index('time') 32 | 33 | self.tag_reader = devicely.TimeStampReader(self.READ_PATH) 34 | 35 | def test_read(self): 36 | pd.testing.assert_frame_equal(self.tag_reader.data, self.data) 37 | 38 | def test_write(self): 39 | with open(self.READ_PATH, 'r') as f: 40 | read_file_contents = f.readlines() 41 | self.tag_reader.write(self.WRITE_PATH) 42 | with open(self.WRITE_PATH, 'r') as f: 43 | write_file_contents = f.readlines() 44 | self.assertEqual(read_file_contents, write_file_contents) 45 | 46 | os.remove(self.WRITE_PATH) 47 | 48 | def test_random_timeshift(self): 49 | old_time_column = self.tag_reader.data.index.copy() 50 | self.tag_reader.timeshift() 51 | new_time_column = self.tag_reader.data.index 52 | 53 | self.assertTrue((old_time_column - pd.Timedelta('730 days') <= new_time_column).all()) 54 | self.assertTrue((new_time_column <= old_time_column - pd.Timedelta('30 days')).all()) 55 | 56 | 57 | 58 | if __name__ == '__main__': 59 | unittest.main() 60 | -------------------------------------------------------------------------------- /docs/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /devicely/time_stamp.py: -------------------------------------------------------------------------------- 1 | """ 2 | TimeStamp for Android allows you to record the timestamp of an event at the time 3 | it occurs. It also allows you to create specific tags such as "Running" or 4 | "Walking" and timestamp those specific activities. 5 | """ 6 | 7 | import random 8 | 9 | import pandas as pd 10 | 11 | class TimeStampReader: 12 | """ 13 | Read, timeshift and write data generated by the Android app TimeStamp 14 | 15 | Attributes 16 | ---------- 17 | data : DataFrame 18 | DataFrame with datetime index and 'tag' column. 19 | """ 20 | 21 | def __init__(self, path): 22 | """ 23 | Read the csv generated by the app and saves the parsed DataFrame. 24 | 25 | Parameters 26 | ---------- 27 | path : str 28 | """ 29 | 30 | self.data = pd.read_csv(path, names=['tag_number', 'time', 'tag']) 31 | self.data['time'] = pd.to_datetime(self.data['time'], format='%Y/%m/%d(%a) %H:%M:%S') 32 | self.data.set_index('time', inplace=True, verify_integrity=True) 33 | self.data.sort_index(inplace=True) 34 | 35 | def write(self, path): 36 | """ 37 | Write the DataFrame stored in 'data' to 'path' 38 | in the same format as it was read. 39 | 40 | Parameters 41 | ---------- 42 | path : str 43 | Path to writing csv. Writing mode: 'w' 44 | """ 45 | 46 | df_to_write = self.data.reset_index()[['tag_number', 'time', 'tag']] 47 | df_to_write.time = df_to_write.time.dt.strftime("%Y/%-m/%-d(%a)\u3000%H:%M:%S").str.lower() 48 | df_to_write.to_csv(path, header=None, index=None, line_terminator='\n') 49 | 50 | def timeshift(self, shift='random'): 51 | """ 52 | Timeshift the data by shifting the index. 53 | 54 | Parameters 55 | ---------- 56 | shift : None/'random', pd.Timestamp or pd.Timedelta 57 | If shift is not specified, shifts the 'time' column by a random time 58 | interval between one month and two years to the past. 59 | 60 | If shift is a timdelta, shift the index by that timedelta. 61 | 62 | If shift is a timestamp, shifts the data such that the earliest entry 63 | has that timestamp. The remaining values will mantain the same 64 | time difference to the first entry. 65 | """ 66 | 67 | if shift == 'random': 68 | one_month = pd.Timedelta('30 days').value 69 | two_years = pd.Timedelta('730 days').value 70 | random_timedelta = - pd.Timedelta(random.uniform(one_month, two_years)).round('s') 71 | self.timeshift(random_timedelta) 72 | if isinstance(shift, pd.Timedelta): 73 | self.data.index += shift 74 | if isinstance(shift, pd.Timestamp): 75 | timedeltas = self.data.index - self.data.index[0] 76 | self.data.index = shift + timedeltas 77 | -------------------------------------------------------------------------------- /docs/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /sphinx/index.rst: -------------------------------------------------------------------------------- 1 | devicely, a python package for reading, timeshifting and writing sensor data 2 | ============================================================================ 3 | 4 | The devicely package is made for reading, writing and de-identifying health sensor data from several sensors: 5 | 6 | * `Empatica E4 `_ is a wearable device that offers real-time physiological data acquisition such as blood volume pulse, electrodermal activity (EDA), heart rate, interbeat intervals, 3-axis acceleration and skin temperature. 7 | * `Biovotion Everion `_ is a wearable device used for the continuous monitoring of vital signs. Currently, it measures the following vital signs: heart rate, blood pulse wave, heart rate variability, activity, SPO2, blood perfusion, respiration rate, steps, energy expenditure, skin temperature, EDA / galvanic skin response (GSR), barometric pressure and sleep. 8 | * `1-lead ECG monitor Faros 180 from Bittium `_ is a one channel ECG monitor with sampling frequency up to 1000 Hz and a 3D acceleration sampling up to 100Hz. 9 | * `Spacelabs (SL 90217) `_ is an oscillometric blood pressure (BP) monitor which can be used to automatically track a person's BP in specificed time intervals. 10 | * `TimeStamp for Android `_ allows you to record the timestamp of an event at the time it occurs. It also allows you to create specific tags such as "Running" or "Walking" and timestamp those specific activities. 11 | * `Shimmer Consensys GSR `_ is a device that is used to collect sensor data in real time and it contains sensors such as GSR / EDA, photoplethysmography (PPG), 3-axis accelerometer, 3-axis gyroscope, 3-axis magnetometer & integrated altimeter. 12 | 13 | 14 | * `Muse S `_ is a consumer grade headband consisting of 4 electrodes electroencephalography (EEG) sensors, 3-axis accelerometer (ACC), gyroscope, and photoplethysmography (PPG) sensors. 15 | 16 | devicely makes it easy to read sensor data and access it in common formats such as dataframes. 17 | 18 | We provide a custom reader class for each sensor which has three core methods: read, timeshift and write. 19 | The timeshift method changes the time of measurement, thereby automatically 20 | helping to de-identify the data. 21 | 22 | After timeshifting, you can write the data in the original data format. 23 | Now you have one more safety measure in place that will facilitate data sharing. 24 | 25 | 26 | Usage 27 | ----- 28 | 29 | - :doc:`examples` 30 | - :doc:`moduleref` 31 | - `Example notebook and data `_ 32 | 33 | About devicely 34 | -------------- 35 | 36 | - `GitHub `_ 37 | - `PyPi `_ 38 | - :doc:`contribution` 39 | 40 | 41 | .. toctree:: 42 | :hidden: 43 | 44 | Quick Start Guide 45 | moduleref 46 | contribution 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | devicely, a python package for reading, timeshifting and writing sensor data 2 | ============================================================================ 3 | 4 | The devicely package is made for reading, writing and de-identifying health sensor data from several sensors: 5 | 6 | * `Empatica E4 `_ is a wearable device that offers real-time physiological data acquisition such as blood volume pulse, electrodermal activity (EDA), heart rate, interbeat intervals, 3-axis acceleration and skin temperature. 7 | * `Biovotion Everion `_ is a wearable device used for the continuous monitoring of vital signs. Currently, it measures the following vital signs: heart rate, blood pulse wave, heart rate variability, activity, SPO2, blood perfusion, respiration rate, steps, energy expenditure, skin temperature, EDA / galvanic skin response (GSR), barometric pressure and sleep. 8 | * `1-lead ECG monitor Faros 180 from Bittium `_ is a one channel ECG monitor with sampling frequency up to 1000 Hz and a 3D acceleration sampling up to 100Hz. 9 | * `Spacelabs (SL 90217) `_ is an oscillometric blood pressure (BP) monitor which can be used to automatically track a person's BP in specificed time intervals. 10 | * `TimeStamp for Android `_ allows you to record the timestamp of an event at the time it occurs. It also allows you to create specific tags such as "Running" or "Walking" and timestamp those specific activities. 11 | * `Shimmer Consensys GSR `_ is a device that is used to collect sensor data in real time and it contains sensors such as GSR / EDA, photoplethysmography (PPG), 3-axis accelerometer, 3-axis gyroscope, 3-axis magnetometer & integrated altimeter. 12 | 13 | 14 | * `Muse S `_ is a consumer grade headband consisting of 4 electrodes electroencephalography (EEG) sensors, 3-axis accelerometer (ACC), gyroscope, and photoplethysmography (PPG) sensors. 15 | 16 | devicely makes it easy to read sensor data and access it in common formats such as dataframes. 17 | 18 | We provide a custom reader class for each sensor which has three core methods: read, timeshift and write. 19 | The timeshift method changes the time of measurement, thereby automatically 20 | helping to de-identify the data. 21 | 22 | After timeshifting, you can write the data in the original data format. 23 | Now you have one more safety measure in place that will facilitate data sharing. 24 | 25 | 26 | Usage 27 | ----- 28 | 29 | - :doc:`examples` 30 | - :doc:`moduleref` 31 | - `Example notebook and data `_ 32 | 33 | About devicely 34 | -------------- 35 | 36 | - `GitHub `_ 37 | - `PyPi `_ 38 | - :doc:`contribution` 39 | 40 | 41 | .. toctree:: 42 | :hidden: 43 | 44 | Quick Start Guide 45 | moduleref 46 | contribution 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/Faros_test_data/testdir_read/HRV.csv: -------------------------------------------------------------------------------- 1 | HRV 2 | 0.0 3 | 0.0 4 | 0.0 5 | 0.0 6 | 0.0 7 | 0.0 8 | 0.0 9 | 0.0 10 | 0.0 11 | 0.0 12 | 0.0 13 | 0.0 14 | 0.0 15 | 0.0 16 | 0.0 17 | 0.0 18 | 0.0 19 | 0.0 20 | 0.0 21 | 0.0 22 | 0.0 23 | 0.0 24 | 0.0 25 | 0.0 26 | 0.0 27 | 0.0 28 | 0.0 29 | 0.0 30 | 0.0 31 | 0.0 32 | 0.0 33 | 0.0 34 | 0.0 35 | 0.0 36 | 0.0 37 | 0.0 38 | 0.0 39 | 0.0 40 | 0.0 41 | 0.0 42 | 0.0 43 | 0.0 44 | 0.0 45 | 0.0 46 | 0.0 47 | 0.0 48 | 0.0 49 | 0.0 50 | 0.0 51 | 0.0 52 | 0.0 53 | 0.0 54 | 0.0 55 | 0.0 56 | 0.0 57 | 0.0 58 | 0.0 59 | 0.0 60 | 0.0 61 | 0.0 62 | 0.0 63 | 0.0 64 | 0.0 65 | 0.0 66 | 0.0 67 | 0.0 68 | 0.0 69 | 0.0 70 | 0.0 71 | 0.0 72 | 0.0 73 | 0.0 74 | 0.0 75 | 0.0 76 | 0.0 77 | 0.0 78 | 0.0 79 | 0.0 80 | 0.0 81 | 0.0 82 | 0.0 83 | 0.0 84 | 0.0 85 | 0.0 86 | 0.0 87 | 0.0 88 | 0.0 89 | 0.0 90 | 0.0 91 | 0.0 92 | 0.0 93 | 0.0 94 | 0.0 95 | 0.0 96 | 0.0 97 | 0.0 98 | 0.0 99 | 0.0 100 | 0.0 101 | 0.0 102 | 0.0 103 | 0.0 104 | 0.0 105 | 0.0 106 | 0.0 107 | 0.0 108 | 0.0 109 | 6460.0 110 | 0.0 111 | 356.0 112 | 0.0 113 | 0.0 114 | 0.0 115 | 0.0 116 | 0.0 117 | 1218.0 118 | 0.0 119 | 0.0 120 | 0.0 121 | 0.0 122 | 1022.0 123 | 0.0 124 | 0.0 125 | 0.0 126 | 0.0 127 | 0.0 128 | 0.0 129 | 0.0 130 | 0.0 131 | 0.0 132 | 0.0 133 | 0.0 134 | 0.0 135 | 0.0 136 | 0.0 137 | 0.0 138 | 3048.0 139 | 0.0 140 | 0.0 141 | 0.0 142 | 0.0 143 | 0.0 144 | 0.0 145 | 0.0 146 | 0.0 147 | 0.0 148 | 0.0 149 | 0.0 150 | 0.0 151 | 0.0 152 | 0.0 153 | 0.0 154 | 0.0 155 | 0.0 156 | 0.0 157 | 0.0 158 | 0.0 159 | 0.0 160 | 0.0 161 | 0.0 162 | 0.0 163 | 0.0 164 | 0.0 165 | 0.0 166 | 0.0 167 | 0.0 168 | 0.0 169 | 0.0 170 | 0.0 171 | 0.0 172 | 0.0 173 | 0.0 174 | 0.0 175 | 0.0 176 | 0.0 177 | 0.0 178 | 0.0 179 | 0.0 180 | 0.0 181 | 0.0 182 | 0.0 183 | 0.0 184 | 0.0 185 | 0.0 186 | 0.0 187 | 0.0 188 | 0.0 189 | 0.0 190 | 0.0 191 | 0.0 192 | 0.0 193 | 0.0 194 | 0.0 195 | 0.0 196 | 0.0 197 | 0.0 198 | 0.0 199 | 0.0 200 | 12614.0 201 | 0.0 202 | 0.0 203 | 0.0 204 | 0.0 205 | 0.0 206 | 0.0 207 | 0.0 208 | 0.0 209 | 0.0 210 | 0.0 211 | 2038.0 212 | 0.0 213 | 0.0 214 | 0.0 215 | 0.0 216 | 0.0 217 | 0.0 218 | 0.0 219 | 0.0 220 | 0.0 221 | 2046.0 222 | 0.0 223 | 362.0 224 | 300.0 225 | 0.0 226 | 0.0 227 | 0.0 228 | 0.0 229 | 0.0 230 | 0.0 231 | 0.0 232 | 0.0 233 | 0.0 234 | 0.0 235 | 0.0 236 | 0.0 237 | 0.0 238 | 0.0 239 | 0.0 240 | 3216.0 241 | 0.0 242 | 0.0 243 | 0.0 244 | 0.0 245 | 0.0 246 | 0.0 247 | 0.0 248 | 0.0 249 | 0.0 250 | 0.0 251 | 0.0 252 | 0.0 253 | 0.0 254 | 0.0 255 | 2854.0 256 | 0.0 257 | 0.0 258 | 0.0 259 | 0.0 260 | 0.0 261 | 0.0 262 | 0.0 263 | 0.0 264 | 0.0 265 | 0.0 266 | 0.0 267 | 0.0 268 | 0.0 269 | 0.0 270 | 0.0 271 | 0.0 272 | 0.0 273 | 0.0 274 | 3968.0 275 | 0.0 276 | 0.0 277 | 0.0 278 | 0.0 279 | 0.0 280 | 0.0 281 | 0.0 282 | 0.0 283 | 0.0 284 | 0.0 285 | 0.0 286 | 0.0 287 | 0.0 288 | 0.0 289 | 0.0 290 | 0.0 291 | 0.0 292 | 3554.0 293 | 0.0 294 | 0.0 295 | 0.0 296 | 0.0 297 | 0.0 298 | 0.0 299 | 0.0 300 | 0.0 301 | 0.0 302 | 0.0 303 | 0.0 304 | 0.0 305 | 0.0 306 | 0.0 307 | 0.0 308 | 0.0 309 | 0.0 310 | 0.0 311 | 3608.0 312 | 0.0 313 | 0.0 314 | 0.0 315 | 0.0 316 | 0.0 317 | 0.0 318 | 0.0 319 | 0.0 320 | 0.0 321 | 0.0 322 | 0.0 323 | 0.0 324 | 0.0 325 | 0.0 326 | 0.0 327 | 0.0 328 | 3452.0 329 | 0.0 330 | 0.0 331 | 0.0 332 | 0.0 333 | 0.0 334 | 0.0 335 | 0.0 336 | 0.0 337 | 0.0 338 | 2064.0 339 | 0.0 340 | 0.0 341 | 0.0 342 | 820.0 343 | 0.0 344 | 0.0 345 | 0.0 346 | 0.0 347 | 0.0 348 | 0.0 349 | 0.0 350 | 0.0 351 | 0.0 352 | 0.0 353 | 0.0 354 | 0.0 355 | 0.0 356 | 0.0 357 | 0.0 358 | 0.0 359 | 0.0 360 | 0.0 361 | 0.0 362 | 0.0 363 | 0.0 364 | 4408.0 365 | 0.0 366 | 0.0 367 | 0.0 368 | 0.0 369 | 0.0 370 | 0.0 371 | 0.0 372 | 0.0 373 | 0.0 374 | 0.0 375 | 0.0 376 | 0.0 377 | 0.0 378 | 0.0 379 | 3044.0 380 | 0.0 381 | 0.0 382 | 0.0 383 | 0.0 384 | 0.0 385 | 0.0 386 | 0.0 387 | 0.0 388 | 0.0 389 | 0.0 390 | 0.0 391 | 0.0 392 | -------------------------------------------------------------------------------- /inst/devicely_structure.tex: -------------------------------------------------------------------------------- 1 | \documentclass[aspectratio=169,handout]{beamer} 2 | 3 | \usepackage[utf8]{inputenc} 4 | \usepackage[ngerman]{babel} 5 | \usepackage{graphicx} 6 | 7 | % \usepackage{enumitem} 8 | % \usepackage{booktabs} 9 | 10 | %\usepackage{color} 11 | \usepackage{tikz} 12 | \usetikzlibrary{trees} 13 | \usepackage{fontawesome} 14 | \usepackage{verbatimbox} 15 | 16 | \usepackage[export]{adjustbox} 17 | 18 | \usepackage{fancyvrb} 19 | 20 | \usepackage{listings} 21 | \usepackage{xcolor} 22 | 23 | 24 | 25 | 26 | 27 | % document shape for individual files 28 | \makeatletter 29 | \pgfdeclareshape{document}{ 30 | \inheritsavedanchors[from=rectangle] % this is nearly a rectangle 31 | \inheritanchorborder[from=rectangle] 32 | \inheritanchor[from=rectangle]{center} 33 | \inheritanchor[from=rectangle]{north} 34 | \inheritanchor[from=rectangle]{south} 35 | \inheritanchor[from=rectangle]{west} 36 | \inheritanchor[from=rectangle]{east} 37 | % ... and possibly more 38 | \backgroundpath{% this is new 39 | % store lower right in xa/ya and upper right in xb/yb 40 | \southwest \pgf@xa=\pgf@x \pgf@ya=\pgf@y 41 | \northeast \pgf@xb=\pgf@x \pgf@yb=\pgf@y 42 | % compute corner of ‘‘flipped page’’ 43 | \pgf@xc=\pgf@xb \advance\pgf@xc by-10pt % this should be a parameter 44 | \pgf@yc=\pgf@yb \advance\pgf@yc by-10pt 45 | % construct main path 46 | \pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}} 47 | \pgfpathlineto{\pgfpoint{\pgf@xa}{\pgf@yb}} 48 | \pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yb}} 49 | \pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yc}} 50 | \pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@ya}} 51 | \pgfpathclose 52 | % add little corner 53 | \pgfpathmoveto{\pgfpoint{\pgf@xc}{\pgf@yb}} 54 | \pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}} 55 | \pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yc}} 56 | \pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}} 57 | } 58 | } 59 | \makeatother 60 | 61 | 62 | \usetheme{metropolis} 63 | 64 | 65 | % \newsavebox\mybox 66 | 67 | \begin{document} 68 | 69 | \newcommand{\FTdir}{} 70 | \def\FTdir(#1,#2,#3){% 71 | \FTfile(#1,{{\color{black!40!white}\faFolderOpen}\hspace{0.2em}#3}) 72 | (tmp.west)++(0.8em,-0.4em)node(#2){} 73 | (tmp.west)++(1.5em,0) 74 | ++(0,-1.3em) 75 | } 76 | 77 | \newcommand{\FTfile}{} 78 | \def\FTfile(#1,#2){% 79 | node(tmp){} 80 | (#1|-tmp)++(0.6em,0) 81 | node(tmp)[anchor=west,black]{#2} 82 | (#1)|-(tmp.west) 83 | ++(0,-1.2em) 84 | } 85 | 86 | \newcommand{\FTroot}{} 87 | \def\FTroot{tmp.west} 88 | 89 | \tikzstyle{doc}=[% 90 | draw, 91 | align=center, 92 | color=black, 93 | shape=document, 94 | minimum width=20mm, 95 | minimum height=15mm, 96 | shape=document, 97 | inner sep=2ex, 98 | ] 99 | 100 | 101 | 102 | \begin{myverbbox}{\reader} 103 | class EverionReader: 104 | def __init__(self, path): 105 | ... 106 | def timeshift(self, interval): 107 | ... 108 | def write(self, path): 109 | ... 110 | \end{myverbbox} 111 | 112 | \begin{myverbbox}{\initpy} 113 | from empatica import EmpaticaReader 114 | from spacelabs import SpacelabsReader 115 | ... 116 | \end{myverbbox} 117 | 118 | % devicely 119 | \begin{frame}[fragile] 120 | \begin{tikzpicture}% 121 | \draw[color=black!60!white] 122 | \FTdir(\FTroot,root,devicely){ 123 | \FTfile(root,\_\_init\_\_.py) 124 | \FTfile(root,empatica.py) 125 | \FTfile(root,spacelabs.py) 126 | \FTfile(root,everion.py) 127 | \FTfile(root,faros.py) 128 | \FTfile(root,shimmer\_plus.py) 129 | }; 130 | 131 | 132 | 133 | \node[doc] at (9,0.5) (initpy) { 134 | \initpy 135 | }; 136 | \draw[loosely dashed] (2.5,-0.5) -- (initpy.west); 137 | 138 | \node[doc] at (9,-3) (everion) { 139 | \reader 140 | }; 141 | \draw[loosely dashed] (2.6,-1.9) -- (everion.west); 142 | 143 | \end{tikzpicture} 144 | \end{frame} 145 | 146 | \end{document} -------------------------------------------------------------------------------- /docs/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos,linux,python,windows,jupyternotebook,visualstudiocode 2 | # Edit at https://www.gitignore.io/?templates=macos,linux,python,windows,jupyternotebook,visualstudiocode 3 | 4 | ### JupyterNotebook ### 5 | .ipynb_checkpoints 6 | */.ipynb_checkpoints/* 7 | 8 | # Remove previous ipynb_checkpoints 9 | # git rm -r .ipynb_checkpoints/ 10 | # 11 | 12 | ### Linux ### 13 | *~ 14 | 15 | # Temporary files which can be created if a process still has a handle open of a deleted file 16 | .fuse_hidden* 17 | 18 | # KDE directory preferences 19 | .directory 20 | 21 | # Linux trash folder which might appear on any partition or disk 22 | .Trash-* 23 | 24 | # .nfs files are created when an open file is removed but is still being accessed 25 | .nfs* 26 | 27 | ### macOS ### 28 | # General 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | # Icon must end with two \r 34 | Icon 35 | 36 | # Thumbnails 37 | ._* 38 | 39 | # Files that might appear in the root of a volume 40 | .DocumentRevisions-V100 41 | .fseventsd 42 | .Spotlight-V100 43 | .TemporaryItems 44 | .Trashes 45 | .VolumeIcon.icns 46 | .com.apple.timemachine.donotpresent 47 | 48 | # Directories potentially created on remote AFP share 49 | .AppleDB 50 | .AppleDesktop 51 | Network Trash Folder 52 | Temporary Items 53 | .apdisk 54 | 55 | ### Python ### 56 | # Byte-compiled / optimized / DLL files 57 | __pycache__/ 58 | *.py[cod] 59 | *$py.class 60 | 61 | # C extensions 62 | *.so 63 | 64 | # Distribution / packaging 65 | .Python 66 | build/ 67 | develop-eggs/ 68 | dist/ 69 | downloads/ 70 | eggs/ 71 | .eggs/ 72 | lib/ 73 | lib64/ 74 | parts/ 75 | sdist/ 76 | var/ 77 | wheels/ 78 | pip-wheel-metadata/ 79 | share/python-wheels/ 80 | *.egg-info/ 81 | .installed.cfg 82 | *.egg 83 | MANIFEST 84 | 85 | # PyInstaller 86 | # Usually these files are written by a python script from a template 87 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 88 | *.manifest 89 | *.spec 90 | 91 | # Installer logs 92 | pip-log.txt 93 | pip-delete-this-directory.txt 94 | 95 | # Unit test / coverage reports 96 | htmlcov/ 97 | .tox/ 98 | .nox/ 99 | .coverage 100 | .coverage.* 101 | .cache 102 | nosetests.xml 103 | coverage.xml 104 | *.cover 105 | .hypothesis/ 106 | .pytest_cache/ 107 | 108 | # Translations 109 | *.mo 110 | *.pot 111 | 112 | # Scrapy stuff: 113 | .scrapy 114 | 115 | # Sphinx documentation 116 | sphinx/_build 117 | docs/.buildinfo 118 | 119 | # PyBuilder 120 | target/ 121 | 122 | # IPython 123 | profile_default/ 124 | ipython_config.py 125 | 126 | # pyenv 127 | .python-version 128 | 129 | # pipenv 130 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 131 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 132 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 133 | # install all needed dependencies. 134 | #Pipfile.lock 135 | 136 | # celery beat schedule file 137 | celerybeat-schedule 138 | 139 | # SageMath parsed files 140 | *.sage.py 141 | 142 | # Environments 143 | .env 144 | .venv 145 | env/ 146 | venv/ 147 | ENV/ 148 | env.bak/ 149 | venv.bak/ 150 | 151 | #Sphinx _build directory 152 | sphinx/_build 153 | 154 | # Spyder project settings 155 | .spyderproject 156 | .spyproject 157 | 158 | # Rope project settings 159 | .ropeproject 160 | 161 | # mkdocs documentation 162 | /site 163 | 164 | # mypy 165 | .mypy_cache/ 166 | .dmypy.json 167 | dmypy.json 168 | 169 | # Pyre type checker 170 | .pyre/ 171 | 172 | ### VisualStudioCode ### 173 | .vscode/ 174 | !.vscode/settings.json 175 | !.vscode/tasks.json 176 | !.vscode/launch.json 177 | !.vscode/extensions.json 178 | 179 | ### VisualStudioCode Patch ### 180 | # Ignore all local history of files 181 | .history 182 | 183 | ### Windows ### 184 | # Windows thumbnail cache files 185 | Thumbs.db 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # Optional folders 209 | data/ 210 | 211 | # Pycharm config 212 | .idea 213 | 214 | 215 | # End of https://www.gitignore.io/api/macos,linux,python,windows,jupyternotebook,visualstudiocode 216 | -------------------------------------------------------------------------------- /devicely/shimmer_plus.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shimmer Consensys GSR is a device that is used to collect sensor data in real 3 | time and it contains sensors such as GSR / EDA, photoplethysmography (PPG), 4 | 3-axis accelerometer, 3-axis gyroscope, 3-axis magnetometer & integrated 5 | altimeter. 6 | """ 7 | 8 | import random 9 | import re 10 | 11 | import numpy as np 12 | import pandas as pd 13 | 14 | 15 | class ShimmerPlusReader: 16 | """ 17 | Read, timeshift and write data 18 | generated by Shimmer Consensys GSR. 19 | 20 | Attributes 21 | ---------- 22 | data : DataFrame 23 | DataFrame with the read signals. The column 'Shimmer_40AC_Timestamp_Unix_CAL' contains the timestamps of measurement. 24 | 25 | units : pandas.Series 26 | A unit for each column in the dataframe. 27 | """ 28 | def __init__(self, path): 29 | """ 30 | Read the csv file generated by the Shimmer device and saves the 31 | parsed DataFrame. 32 | 33 | Parameters 34 | ---------- 35 | path : str 36 | Path of the csv file. 37 | delimiter, optional : str 38 | Delimiter used in the csv file. 39 | """ 40 | with open(path, 'r') as f: 41 | self.delimiter = re.findall(f'"sep=(.)"', f.readline())[0] 42 | col_names = list(filter(bool, f.readline().strip().split(self.delimiter))) 43 | units = list(filter(bool, f.readline().strip().split(self.delimiter))) 44 | self.units = dict(zip(col_names, units)) 45 | self.data = pd.read_csv(f, sep=self.delimiter, header=None, index_col=False, 46 | names=col_names, 47 | parse_dates=['Shimmer_40AC_Timestamp_Unix_CAL'], 48 | date_parser=lambda x: pd.to_datetime(x, unit='ms').round('ms') 49 | ) 50 | self.data['Shimmer_40AC_Accel_LN_mag'] = np.linalg.norm(self.data[['Shimmer_40AC_Accel_LN_X_CAL', 51 | 'Shimmer_40AC_Accel_LN_Y_CAL', 52 | 'Shimmer_40AC_Accel_LN_Z_CAL']], 53 | axis=1) 54 | 55 | def write(self, path): 56 | """ 57 | Write the DataFrame and units to the 58 | writing path in the same format as it was read. 59 | 60 | Parameters 61 | ---------- 62 | path : str 63 | Path to writing csv file. Writing mode: 'w'. 64 | """ 65 | write_df = self.data.drop(columns=['Shimmer_40AC_Accel_LN_mag']) 66 | write_df['Shimmer_40AC_Timestamp_Unix_CAL'] = write_df['Shimmer_40AC_Timestamp_Unix_CAL'].map(lambda x: float(x.round('ms').value / 1e6)) 67 | units_df = pd.DataFrame(self.units, index=[0]) 68 | write_df = pd.concat([units_df, write_df]) 69 | 70 | with open(path, 'w') as f: 71 | f.write(f'"sep={self.delimiter}"\n') 72 | write_df.to_csv(f, index=False, sep=self.delimiter, line_terminator=f"{self.delimiter}\n") 73 | 74 | def timeshift(self, shift='random'): 75 | """ 76 | Timeshift the data by shifting all time related columns. 77 | 78 | Parameters 79 | ---------- 80 | shift : None/'random', pd.Timestamp or pd.Timedelta 81 | If shift is not specified, shifts the data by a random time interval 82 | between one month and two years to the past. 83 | 84 | If shift is a timdelta, shifts the data by that timedelta. 85 | 86 | If shift is a timestamp, shifts the data such that the earliest entry 87 | has that timestamp. The remaining values will mantain the same 88 | time difference to the first entry. 89 | """ 90 | if shift == 'random': 91 | one_month = pd.Timedelta('30 days').value 92 | two_years = pd.Timedelta('730 days').value 93 | random_timedelta = - pd.Timedelta(random.uniform(one_month, two_years)) 94 | self.timeshift(random_timedelta) 95 | if isinstance(shift, pd.Timestamp): 96 | shift = shift.round('ms') 97 | timedeltas = self.data['Shimmer_40AC_Timestamp_Unix_CAL'] - self.data['Shimmer_40AC_Timestamp_Unix_CAL'].min() 98 | self.data['Shimmer_40AC_Timestamp_Unix_CAL'] = shift + timedeltas 99 | if isinstance(shift, pd.Timedelta): 100 | self.data['Shimmer_40AC_Timestamp_Unix_CAL'] += shift.round('ms') 101 | -------------------------------------------------------------------------------- /tests/Everion_test_data/CsvData_sensor_data_EV-EC39-7079-57A8.csv: -------------------------------------------------------------------------------- 1 | count,streamType,tag,time,values 2 | 22917264,16,86,1551458001,2176.0 3 | 22917264,16,81,1551458001,51612.0 4 | 22917264,16,80,1551458001,668.0 5 | 22917264,16,83,1551458001,26377.0 6 | 22917264,16,85,1551458001,1232.0 7 | 22917264,16,84,1551458001,-3728.0 8 | 22917264,16,82,1551458001,25728.0 9 | 22917263,16,86,1551458001,2064.0 10 | 22917263,16,81,1551458001,51584.0 11 | 22917263,16,80,1551458001,665.0 12 | 22917263,16,83,1551458001,26396.0 13 | 22917263,16,85,1551458001,1024.0 14 | 22917263,16,84,1551458001,-3232.0 15 | 22917263,16,82,1551458001,25732.0 16 | 22917262,16,86,1551458001,2192.0 17 | 22917262,16,81,1551458001,51538.0 18 | 22917262,16,80,1551458001,659.0 19 | 22917262,16,83,1551458001,26421.0 20 | 22917262,16,85,1551458001,944.0 21 | 22917262,16,84,1551458001,-3120.0 22 | 22917262,16,82,1551458001,25728.0 23 | 22917261,16,86,1551458001,2096.0 24 | 22917261,16,81,1551458001,51481.0 25 | 22917261,16,80,1551458001,665.0 26 | 22917261,16,83,1551458001,26360.0 27 | 22917261,16,85,1551458001,576.0 28 | 22917261,16,84,1551458001,-3152.0 29 | 22917261,16,82,1551458001,25704.0 30 | 22917260,16,86,1551458001,2080.0 31 | 22917260,16,81,1551458001,51489.0 32 | 22917260,16,80,1551458001,669.0 33 | 22917260,16,83,1551458001,26377.0 34 | 22917260,16,85,1551458001,784.0 35 | 22917260,16,84,1551458001,-3216.0 36 | 22917260,16,82,1551458001,25722.0 37 | 22917259,16,86,1551458001,2160.0 38 | 22917259,16,81,1551458001,51403.0 39 | 22917259,16,80,1551458001,644.0 40 | 22917259,16,83,1551458001,26352.0 41 | 22917259,16,85,1551458001,1072.0 42 | 22917259,16,84,1551458001,-3360.0 43 | 22917259,16,82,1551458001,25719.0 44 | 22917258,16,86,1551458001,2048.0 45 | 22917258,16,81,1551458001,51370.0 46 | 22917258,16,80,1551458001,657.0 47 | 22917258,16,83,1551458001,26350.0 48 | 22917258,16,85,1551458001,1040.0 49 | 22917258,16,84,1551458001,-3248.0 50 | 22917258,16,82,1551458001,25708.0 51 | 22917271,16,86,1551458001,3840.0 52 | 22917271,16,81,1551458001,8317.0 53 | 22917271,16,80,1551458001,2230.0 54 | 22917271,16,83,1551458001,4343.0 55 | 22917271,16,85,1551458001,-592.0 56 | 22917271,16,84,1551458001,-3536.0 57 | 22917271,16,82,1551458001,4170.0 58 | 22917270,16,86,1551458001,3392.0 59 | 22917270,16,81,1551458001,16539.0 60 | 22917270,16,80,1551458001,1273.0 61 | 22917270,16,83,1551458001,6197.0 62 | 22917270,16,85,1551458001,-368.0 63 | 22917270,16,84,1551458001,-3424.0 64 | 22917270,16,82,1551458001,5843.0 65 | 22917269,16,86,1551458001,2688.0 66 | 22917269,16,81,1551458001,40326.0 67 | 22917269,16,80,1551458001,1096.0 68 | 22917269,16,83,1551458001,11687.0 69 | 22917269,16,85,1551458001,-272.0 70 | 22917269,16,84,1551458001,-3248.0 71 | 22917269,16,82,1551458001,11411.0 72 | 22917268,16,86,1551458001,2944.0 73 | 22917268,16,81,1551458001,65532.0 74 | 22917268,16,80,1551458001,754.0 75 | 22917268,16,83,1551458001,23342.0 76 | 22917268,16,85,1551458001,512.0 77 | 22917268,16,84,1551458001,-3296.0 78 | 22917268,16,82,1551458001,23636.0 79 | 22917267,16,86,1551458001,3456.0 80 | 22917267,16,81,1551458001,51846.0 81 | 22917267,16,80,1551458001,665.0 82 | 22917267,16,83,1551458001,26635.0 83 | 22917267,16,85,1551458001,1072.0 84 | 22917267,16,84,1551458001,-3872.0 85 | 22917267,16,82,1551458001,25849.0 86 | 22917266,16,86,1551458001,2368.0 87 | 22917266,16,81,1551458001,51739.0 88 | 22917266,16,80,1551458001,663.0 89 | 22917266,16,83,1551458001,26436.0 90 | 22917266,16,85,1551458001,336.0 91 | 22917266,16,84,1551458001,-3760.0 92 | 22917266,16,82,1551458001,25757.0 93 | 22917265,16,86,1551458001,2224.0 94 | 22917265,16,81,1551458001,51689.0 95 | 22917265,16,80,1551458001,662.0 96 | 22917265,16,83,1551458001,26407.0 97 | 22917265,16,85,1551458001,896.0 98 | 22917265,16,84,1551458001,-3728.0 99 | 22917265,16,82,1551458001,25744.0 100 | 22917274,16,86,1551458001,2352.0 101 | 22917274,16,81,1551458001,7412.0 102 | 22917274,16,80,1551458001,6393.0 103 | 22917274,16,83,1551458001,6702.0 104 | 22917274,16,85,1551458001,576.0 105 | 22917274,16,84,1551458001,-2064.0 106 | 22917274,16,82,1551458001,6744.0 107 | 22917273,16,86,1551458001,4240.0 108 | 22917273,16,81,1551458001,6893.0 109 | 22917273,16,80,1551458001,5421.0 110 | 22917273,16,83,1551458001,5893.0 111 | 22917273,16,85,1551458001,160.0 112 | 22917273,16,84,1551458001,-3200.0 113 | 22917273,16,82,1551458001,5887.0 114 | 22917272,16,86,1551458001,4672.0 115 | 22917272,16,81,1551458001,6395.0 116 | 22917272,16,80,1551458001,3727.0 117 | 22917272,16,83,1551458001,4656.0 118 | 22917272,16,85,1551458001,-480.0 119 | 22917272,16,84,1551458001,-3760.0 120 | 22917272,16,82,1551458001,4671.0 121 | 22917258,16,89,1551458001,99.0 122 | 22917258,16,88,1551458001,227.0 123 | 22917258,16,90,1551458001,99.0 124 | 22917258,16,91,1551458001,0.0 -------------------------------------------------------------------------------- /docs/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}t.length>0&&($(".wy-menu-vertical .current").removeClass("current"),t.addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l1").parent().addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current"),t.closest("li.toctree-l5").addClass("current"),t[0].scrollIntoView())}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t`_ and open a pull request when you are done with your changes. 6 | 7 | Getting started with development 8 | -------------------------------- 9 | 10 | If you wonder why we do not have a ``setup.py``, ``setup.cfg`` or ``requirements.txt``, it is because we use `poetry `_ for packaging, building and dependency management. 11 | To get a development environment, clone the repository and exeute ``poetry install``. This will create a virtual environment for the project and install all runtime- and development dependencies. 12 | Now you can run the tests with ``poetry run pytest``, work on the example jupyter notebook with ``poetry run jupyter notebook`` or enter the virtual environment with ``poetry shell``. 13 | 14 | Add a sensor class 15 | ------------------ 16 | 17 | One reason why you might want to contribute to devicely is to add a new sensor class to the package. 18 | Please follow these steps if that is the case: 19 | 20 | Your sensor class needs to have its own module in the devicely package: 21 | 22 | .. image:: devicely_structure.png 23 | :width: 600 24 | :alt: devicely package structure 25 | 26 | Please create a class member for each signal that your sensor records. To provide an example, if your sensor contains heart rate and acceleration data, 27 | use uppercase attribute names like ``reader.ACC`` and ``reader.HR``. These attributes should be dataframes, indexed by time of measurement. 28 | For metadata, you can use basic data structures such as dictionaries. 29 | Apart from the individual signal dataframes, your sensor should have an attribute ``reader.data`` which is a joined dataframe of all individual signal dataframes. 30 | 31 | The ``timeshift`` method should accept three types of parameters: nothing, a ``pandas.Timestamp`` or a ``pandas.Timedelta``. 32 | With no parameter, all time-related data attributes are shifted between one month and two years to the past. 33 | With a ``pandas.Timedelta``, all attributes are shifted by adding the timedelta to them. 34 | With a ``pandas.Timestamp``, all attributes are shifted such that the provided timestamp is the earliest data entry and all other data entries keep the same distance to it. 35 | 36 | Timeshifting is not the only way to anonymize data. If your sensor uses other metadata such as a patient id, please add a method ``deidentify`` to clear such metadata. 37 | You can look at ``devicely.SpacelabsReader.deidentify`` for an example. 38 | 39 | With the ``reader.write`` method, users can write the identified data to be persistent, ideally in the same original sensor format. 40 | Keeping the original format is not strictly necessary, just make sure that your reader class can be instantiated with the written data. 41 | 42 | Write unit tests 43 | ---------------- 44 | 45 | Unit tests ensure that our sensor classes work the way we expect them to, which is why we aim for a high test coverage. 46 | The existing test cases are a good example to see how your test case should be structured. 47 | In general, have one test case for your sensor and one directory with data for testing. 48 | Write test methods for the most important use cases of your sensor, e.g. reading, timeshifting and writing. 49 | Please make sure that your test cases run fairly quickly as this helps us run tests locally and keep the GitHub Actions jobs fast. 50 | It helps to use small sensor files for testing, e.g. only 30 seconds long. 51 | 52 | 53 | Write documentation 54 | ------------------- 55 | 56 | At devicely, we live by the motto **Docs or it didn't happen**. 57 | 58 | Therefore, all sensor classes need to be documented well. 59 | Most importantly, you need to add the following docstrings to your class: 60 | 61 | 1. a docstring on top of the class definition containing information about the sensor and what the most important class attributes are 62 | 2. a docstring for each method that users are meant to call specifying the syntax, parameters and return values 63 | 64 | Apart from docstrings, all you need to do is add example code to the `notebook with examples `_. 65 | Not only is this notebook meant to be run by users themselves, but it is also rendered in our :doc:`examples` documentation section. 66 | 67 | The docstrings and the notebook are basically all you have to do to document your sensor class. Feel free to look at the existing sensors as a guide! 68 | 69 | 70 | Provide example data 71 | -------------------- 72 | 73 | If you want people to try out your reader class, you need to provide example data. 74 | For this purpose we maintain this `example repository with examples and data `_, 75 | to which you can create a PR to supply example data for your sensor. 76 | 77 | 78 | License and Copyright 79 | --------------------- 80 | 81 | Currently **devicely** is licensed under the `MIT license `_ 82 | and all copyright is attributed to the Digital Health Center (Hasso Plattner Institute). 83 | -------------------------------------------------------------------------------- /docs/_sources/contribution.rst.txt: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | Whether you would like to add a new sensor, fix a bug or help with packaging, we would love for you to contribute. 5 | To make a contribution, please fork `our repository `_ and open a pull request when you are done with your changes. 6 | 7 | Getting started with development 8 | -------------------------------- 9 | 10 | If you wonder why we do not have a ``setup.py``, ``setup.cfg`` or ``requirements.txt``, it is because we use `poetry `_ for packaging, building and dependency management. 11 | To get a development environment, clone the repository and exeute ``poetry install``. This will create a virtual environment for the project and install all runtime- and development dependencies. 12 | Now you can run the tests with ``poetry run pytest``, work on the example jupyter notebook with ``poetry run jupyter notebook`` or enter the virtual environment with ``poetry shell``. 13 | 14 | Add a sensor class 15 | ------------------ 16 | 17 | One reason why you might want to contribute to devicely is to add a new sensor class to the package. 18 | Please follow these steps if that is the case: 19 | 20 | Your sensor class needs to have its own module in the devicely package: 21 | 22 | .. image:: devicely_structure.png 23 | :width: 600 24 | :alt: devicely package structure 25 | 26 | Please create a class member for each signal that your sensor records. To provide an example, if your sensor contains heart rate and acceleration data, 27 | use uppercase attribute names like ``reader.ACC`` and ``reader.HR``. These attributes should be dataframes, indexed by time of measurement. 28 | For metadata, you can use basic data structures such as dictionaries. 29 | Apart from the individual signal dataframes, your sensor should have an attribute ``reader.data`` which is a joined dataframe of all individual signal dataframes. 30 | 31 | The ``timeshift`` method should accept three types of parameters: nothing, a ``pandas.Timestamp`` or a ``pandas.Timedelta``. 32 | With no parameter, all time-related data attributes are shifted between one month and two years to the past. 33 | With a ``pandas.Timedelta``, all attributes are shifted by adding the timedelta to them. 34 | With a ``pandas.Timestamp``, all attributes are shifted such that the provided timestamp is the earliest data entry and all other data entries keep the same distance to it. 35 | 36 | Timeshifting is not the only way to anonymize data. If your sensor uses other metadata such as a patient id, please add a method ``deidentify`` to clear such metadata. 37 | You can look at ``devicely.SpacelabsReader.deidentify`` for an example. 38 | 39 | With the ``reader.write`` method, users can write the identified data to be persistent, ideally in the same original sensor format. 40 | Keeping the original format is not strictly necessary, just make sure that your reader class can be instantiated with the written data. 41 | 42 | Write unit tests 43 | ---------------- 44 | 45 | Unit tests ensure that our sensor classes work the way we expect them to, which is why we aim for a high test coverage. 46 | The existing test cases are a good example to see how your test case should be structured. 47 | In general, have one test case for your sensor and one directory with data for testing. 48 | Write test methods for the most important use cases of your sensor, e.g. reading, timeshifting and writing. 49 | Please make sure that your test cases run fairly quickly as this helps us run tests locally and keep the GitHub Actions jobs fast. 50 | It helps to use small sensor files for testing, e.g. only 30 seconds long. 51 | 52 | 53 | Write documentation 54 | ------------------- 55 | 56 | At devicely, we live by the motto **Docs or it didn't happen**. 57 | 58 | Therefore, all sensor classes need to be documented well. 59 | Most importantly, you need to add the following docstrings to your class: 60 | 61 | 1. a docstring on top of the class definition containing information about the sensor and what the most important class attributes are 62 | 2. a docstring for each method that users are meant to call specifying the syntax, parameters and return values 63 | 64 | Apart from docstrings, all you need to do is add example code to the `notebook with examples `_. 65 | Not only is this notebook meant to be run by users themselves, but it is also rendered in our :doc:`examples` documentation section. 66 | 67 | The docstrings and the notebook are basically all you have to do to document your sensor class. Feel free to look at the existing sensors as a guide! 68 | 69 | 70 | Provide example data 71 | -------------------- 72 | 73 | If you want people to try out your reader class, you need to provide example data. 74 | For this purpose we maintain this `example repository with examples and data `_, 75 | to which you can create a PR to supply example data for your sensor. 76 | 77 | 78 | License and Copyright 79 | --------------------- 80 | 81 | Currently **devicely** is licensed under the `MIT license `_ 82 | and all copyright is attributed to the Digital Health Center (Hasso Plattner Institute). 83 | -------------------------------------------------------------------------------- /tests/Everion_test_data/CsvData_attributes_dailys_EV-EC39-7079-57A8.csv: -------------------------------------------------------------------------------- 1 | count,streamType,tag,time,values 2 | 14577,8,67,1551458602,2.0;15.0 3 | 14576,8,67,1551458602,4.0;9.0 4 | 14575,8,67,1551458602,3.0;8.0 5 | 14574,8,67,1551458602,11.0;6.0 6 | 14573,8,67,1551458602,12.0;5.0 7 | 14508,8,105,1551458602,2.0 8 | 14507,8,142,1551458602,23673.0 9 | 14506,8,141,1551458602,24855.0 10 | 14505,8,107,1551458602,23673.0 11 | 14504,8,106,1551458602,76.0 12 | 14503,8,56,1551458602,208.0 13 | 14502,8,55,1551458602,24256.0 14 | 14501,8,54,1551458602,208.0 15 | 14500,8,53,1551458602,-4786.0 16 | 14499,8,52,1551458602,208.0 17 | 14498,8,51,1551458602,24254.0 18 | 14497,8,49,1551458602,65.0 19 | 14496,8,48,1551458602,168.0 20 | 14495,8,47,1551458602,1.0 21 | 14494,8,46,1551458602,31.0 22 | 14523,8,117,1551458602,25.0;15.0 23 | 14522,8,117,1551458602,12.0;9.0 24 | 14521,8,117,1551458602,12.0;5.0 25 | 14520,8,117,1551458602,10.0;2.0 26 | 14519,8,114,1551458602,1.0 27 | 14518,8,94,1551458602,174.0 28 | 14517,8,93,1551458602,46.0 29 | 14516,8,125,1551458602,87.0 30 | 14515,8,124,1551458602,30.0 31 | 14514,8,99,1551458602,4.0 32 | 14513,8,98,1551458602,12.0 33 | 14512,8,97,1551458602,30.0 34 | 14511,8,96,1551458602,47.0 35 | 14510,8,95,1551458602,3.0 36 | 14509,8,104,1551458602,41.0 37 | 14538,8,61,1551458602,48.0;13.0 38 | 14537,8,61,1551458602,43.0;11.0 39 | 14536,8,61,1551458602,47.0;10.0 40 | 14535,8,61,1551458602,13.0;9.0 41 | 14534,8,61,1551458602,32.0;8.0 42 | 14533,8,61,1551458602,39.0;7.0 43 | 14532,8,61,1551458602,62.0;6.0 44 | 14531,8,61,1551458602,31.0;5.0 45 | 14530,8,61,1551458602,40.0;4.0 46 | 14529,8,61,1551458602,40.0;3.0 47 | 14528,8,61,1551458602,44.0;2.0 48 | 14527,8,117,1551458602,911.0;24.0 49 | 14526,8,117,1551458602,35.0;23.0 50 | 14525,8,117,1551458602,29.0;22.0 51 | 14524,8,117,1551458602,19.0;20.0 52 | 14553,8,64,1551458602,57.0;9.0 53 | 14552,8,64,1551458602,39.0;8.0 54 | 14551,8,64,1551458602,45.0;7.0 55 | 14550,8,64,1551458602,33.0;6.0 56 | 14549,8,64,1551458602,24.0;5.0 57 | 14548,8,64,1551458602,24.0;4.0 58 | 14547,8,64,1551458602,27.0;3.0 59 | 14546,8,64,1551458602,12.0;2.0 60 | 14545,8,63,1551458602,89.0;6.0 61 | 14544,8,63,1551458602,61.0;3.0 62 | 14543,8,62,1551458602,4.0;14.0 63 | 14542,8,62,1551458602,3.0;9.0 64 | 14541,8,62,1551458602,9.0;8.0 65 | 14540,8,62,1551458602,1.0;3.0 66 | 14539,8,61,1551458602,32.0;15.0 67 | 14568,8,65,1551458602,1.0;14.0 68 | 14567,8,65,1551458602,8.0;11.0 69 | 14566,8,65,1551458602,1.0;10.0 70 | 14565,8,65,1551458602,41.0;9.0 71 | 14564,8,65,1551458602,14.0;8.0 72 | 14563,8,65,1551458602,16.0;7.0 73 | 14562,8,65,1551458602,17.0;6.0 74 | 14561,8,65,1551458602,24.0;5.0 75 | 14560,8,65,1551458602,7.0;4.0 76 | 14559,8,65,1551458602,27.0;3.0 77 | 14558,8,65,1551458602,3.0;2.0 78 | 14557,8,64,1551458602,12.0;15.0 79 | 14556,8,64,1551458602,12.0;14.0 80 | 14555,8,64,1551458602,43.0;11.0 81 | 14554,8,64,1551458602,33.0;10.0 82 | 14572,8,67,1551458602,3.0;4.0 83 | 14571,8,67,1551458602,17.0;3.0 84 | 14570,8,67,1551458602,2.0;2.0 85 | 14569,8,65,1551458602,2.0;15.0 86 | 14493,8,67,1551443242,2.0;15.0 87 | 14492,8,67,1551443242,4.0;9.0 88 | 14491,8,67,1551443242,3.0;8.0 89 | 14490,8,67,1551443242,11.0;6.0 90 | 14489,8,67,1551443242,12.0;5.0 91 | 14424,8,105,1551443242,2.0 92 | 14423,8,142,1551443242,23673.0 93 | 14422,8,141,1551443242,9496.0 94 | 14421,8,107,1551443242,23673.0 95 | 14420,8,106,1551443242,76.0 96 | 14419,8,56,1551443242,208.0 97 | 14418,8,55,1551443242,24256.0 98 | 14417,8,54,1551443242,208.0 99 | 14416,8,53,1551443242,-4786.0 100 | 14415,8,52,1551443242,208.0 101 | 14414,8,51,1551443242,24254.0 102 | 14413,8,49,1551443242,65.0 103 | 14412,8,48,1551443242,168.0 104 | 14411,8,47,1551443242,1.0 105 | 14410,8,46,1551443242,31.0 106 | 14439,8,117,1551443242,25.0;15.0 107 | 14438,8,117,1551443242,12.0;9.0 108 | 14437,8,117,1551443242,12.0;5.0 109 | 14436,8,117,1551443242,10.0;2.0 110 | 14435,8,114,1551443242,1.0 111 | 14434,8,94,1551443242,174.0 112 | 14433,8,93,1551443242,46.0 113 | 14432,8,125,1551443242,87.0 114 | 14431,8,124,1551443242,30.0 115 | 14430,8,99,1551443242,4.0 116 | 14429,8,98,1551443242,12.0 117 | 14428,8,97,1551443242,30.0 118 | 14427,8,96,1551443242,47.0 119 | 14426,8,95,1551443242,3.0 120 | 14425,8,104,1551443242,41.0 121 | 14454,8,61,1551443242,48.0;13.0 122 | 14453,8,61,1551443242,43.0;11.0 123 | 14452,8,61,1551443242,47.0;10.0 124 | 14451,8,61,1551443242,13.0;9.0 125 | 14450,8,61,1551443242,32.0;8.0 126 | 14449,8,61,1551443242,39.0;7.0 127 | 14448,8,61,1551443242,62.0;6.0 128 | 14447,8,61,1551443242,31.0;5.0 129 | 14446,8,61,1551443242,40.0;4.0 130 | 14445,8,61,1551443242,40.0;3.0 131 | 14444,8,61,1551443242,44.0;2.0 132 | 14443,8,117,1551443242,911.0;24.0 133 | 14442,8,117,1551443242,35.0;23.0 134 | 14441,8,117,1551443242,29.0;22.0 135 | 14440,8,117,1551443242,19.0;20.0 136 | 14469,8,64,1551443242,57.0;9.0 137 | 14468,8,64,1551443242,39.0;8.0 138 | 14467,8,64,1551443242,45.0;7.0 139 | 14466,8,64,1551443242,33.0;6.0 140 | 14465,8,64,1551443242,24.0;5.0 141 | 14464,8,64,1551443242,24.0;4.0 142 | 14463,8,64,1551443242,27.0;3.0 143 | 14462,8,64,1551443242,12.0;2.0 144 | 14461,8,63,1551443242,89.0;6.0 145 | 14460,8,63,1551443242,61.0;3.0 146 | 14459,8,62,1551443242,4.0;14.0 147 | 14458,8,62,1551443242,3.0;9.0 148 | 14457,8,62,1551443242,9.0;8.0 149 | 14456,8,62,1551443242,1.0;3.0 150 | 14455,8,61,1551443242,32.0;15.0 151 | 14484,8,65,1551443242,1.0;14.0 152 | 14483,8,65,1551443242,8.0;11.0 153 | 14482,8,65,1551443242,1.0;10.0 154 | 14481,8,65,1551443242,41.0;9.0 155 | 14480,8,65,1551443242,14.0;8.0 156 | 14479,8,65,1551443242,16.0;7.0 157 | 14478,8,65,1551443242,17.0;6.0 158 | 14477,8,65,1551443242,24.0;5.0 159 | 14476,8,65,1551443242,7.0;4.0 160 | 14475,8,65,1551443242,27.0;3.0 161 | 14474,8,65,1551443242,3.0;2.0 162 | 14473,8,64,1551443242,12.0;15.0 163 | 14472,8,64,1551443242,12.0;14.0 164 | 14471,8,64,1551443242,43.0;11.0 165 | 14470,8,64,1551443242,33.0;10.0 166 | 14488,8,67,1551443242,3.0;4.0 167 | 14487,8,67,1551443242,17.0;3.0 168 | 14486,8,67,1551443242,2.0;2.0 169 | 14485,8,65,1551443242,2.0;15.0 170 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Search — devicely 1.1.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | 110 | 111 |
112 | 113 | 114 | 120 | 121 | 122 |
123 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 | 146 |
    147 | 148 |
  • »
  • 149 | 150 |
  • Search
  • 151 | 152 | 153 |
  • 154 | 155 |
  • 156 | 157 |
158 | 159 | 160 |
161 |
162 |
163 |
164 | 165 | 172 | 173 | 174 |
175 | 176 |
177 | 178 |
179 | 180 |
181 |
182 | 183 |
184 | 185 |
186 |

187 | © Copyright 2021, Digital Health Center (Hasso Plattner Institute). 188 | 189 |

190 |
191 | 192 | 193 | 194 | Built with Sphinx using a 195 | 196 | theme 197 | 198 | provided by Read the Docs. 199 | 200 |
201 |
202 |
203 | 204 |
205 | 206 |
207 | 208 | 209 | 214 | 215 | 216 | 217 | 218 | 219 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /tests/test_muse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the muse module 3 | """ 4 | import os 5 | import sys 6 | import unittest 7 | 8 | import numpy as np 9 | import pandas as pd 10 | 11 | import devicely 12 | 13 | 14 | class MuseTestCase(unittest.TestCase): 15 | READ_PATH = 'tests/Muse_test_data/read_test_data.csv' 16 | WRITE_PATH = 'tests/Muse_test_data/write_test_data.csv' 17 | 18 | expected_data_head = pd.DataFrame( 19 | {'AUX_RIGHT': [844.1392, np.nan, 861.06226, 1009.34064, 1006.52014], 20 | 'Accelerometer_X': [-0.23260498046875, np.nan, -0.23260498046875, -0.23260498046875, -0.23260498046875], 21 | 'Accelerometer_Y': [-0.04901123046875, np.nan, -0.04901123046875, -0.04901123046875, -0.04901123046875], 22 | 'Accelerometer_Z': [0.9664306640625, np.nan, 0.9664306640625, 0.9664306640625, 0.9664306640625], 23 | 'Alpha_AF7': [0.70684004, np.nan, 0.7046354, 0.7046354, 0.7046354], 24 | 'Alpha_AF8': [0.3738289, np.nan, 0.37960157, 0.37960157, 0.37960157], 25 | 'Alpha_TP10': [0.5247787, np.nan, 0.5247787, 0.5247787, 0.5247787], 26 | 'Alpha_TP9': [0.6818201, np.nan, 0.6818201, 0.6818201, 0.6818201], 27 | 'Battery': [100.0, np.nan, 100.0, 100.0, 100.0], 28 | 'Beta_AF7': [0.9047759, np.nan, 0.90967786, 0.90967786, 0.90967786], 29 | 'Beta_AF8': [-0.074241824, np.nan, -0.07182129, -0.07182129, -0.07182129], 30 | 'Beta_TP10': [0.24255054, np.nan, 0.24255054, 0.24255054, 0.24255054], 31 | 'Beta_TP9': [0.40113458, np.nan, 0.40113458, 0.40113458, 0.40113458], 32 | 'Delta_AF7': [0.65365446, np.nan, 0.6321334, 0.6321334, 0.6321334], 33 | 'Delta_AF8': [0.87098247, np.nan, 0.8560632, 0.8560632, 0.8560632], 34 | 'Delta_TP10': [0.7412762, np.nan, 0.7412762, 0.7412762, 0.7412762], 35 | 'Delta_TP9': [0.7765235, np.nan, 0.7765235, 0.7765235, 0.7765235], 36 | 'Elements': [np.nan, '/muse/elements/blink', np.nan, np.nan, np.nan], 37 | 'Gamma_AF7': [0.7472885, np.nan, 0.73841846, 0.73841846, 0.73841846], 38 | 'Gamma_AF8': [-0.29481375, np.nan, -0.29959682, -0.29959682, -0.29959682], 39 | 'Gamma_TP10': [-0.3348679, np.nan, -0.3348679, -0.3348679, -0.3348679], 40 | 'Gamma_TP9': [-0.0852012, np.nan, -0.0852012, -0.0852012, -0.0852012], 41 | 'Gyro_X': [2.998199462890625, np.nan, 2.998199462890625, 2.998199462890625, 2.998199462890625], 42 | 'Gyro_Y': [2.130889892578125, np.nan, 2.130889892578125, 2.130889892578125, 2.130889892578125], 43 | 'Gyro_Z': [1.966400146484375, np.nan, 1.966400146484375, 1.966400146484375, 1.966400146484375], 44 | 'HSI_AF7': [1.0, np.nan, 1.0, 1.0, 1.0], 45 | 'HSI_AF8': [1.0, np.nan, 1.0, 1.0, 1.0], 46 | 'HSI_TP10': [1.0, np.nan, 1.0, 1.0, 1.0], 47 | 'HSI_TP9': [1.0, np.nan, 1.0, 1.0, 1.0], 48 | 'HeadBandOn': [1.0, np.nan, 1.0, 1.0, 1.0], 49 | 'RAW_AF7': [798.60803, np.nan, 796.5934, 805.8608, 805.4579], 50 | 'RAW_AF8': [794.17584, np.nan, 791.75824, 789.7436, 784.5055], 51 | 'RAW_TP10': [835.6777, np.nan, 833.663, 827.2161, 820.3663], 52 | 'RAW_TP9': [832.4542, np.nan, 829.2308, 824.7985, 819.9634], 53 | 'Theta_AF7': [0.328677, np.nan, 0.33023703, 0.33023703, 0.33023703], 54 | 'Theta_AF8': [0.5665972, np.nan, 0.56282604, 0.56282604, 0.56282604], 55 | 'Theta_TP10': [0.5058985, np.nan, 0.5058985, 0.5058985, 0.5058985], 56 | 'Theta_TP9': [0.76928276, np.nan, 0.76928276, 0.76928276, 0.76928276]}, 57 | index=pd.DatetimeIndex(['2021-04-05 15:48:04.834', '2021-04-05 15:48:04.840', '2021-04-05 15:48:04.836', '2021-04-05 15:48:04.842', '2021-04-05 15:48:04.843'], name='TimeStamp') 58 | ) 59 | 60 | def setUp(self): 61 | self.reader = devicely.MuseReader(self.READ_PATH) 62 | 63 | def test_read(self): 64 | pd.testing.assert_frame_equal(self.reader.data.head(), self.expected_data_head, check_like=True) 65 | 66 | def test_write(self): 67 | self.reader.write(self.WRITE_PATH) 68 | new_reader = devicely.MuseReader(self.WRITE_PATH) 69 | pd.testing.assert_frame_equal(new_reader.data, self.reader.data) 70 | os.remove(self.WRITE_PATH) 71 | 72 | def test_timeshift_by_timedelta(self): 73 | shift = pd.Timedelta('1 days, 2 hours, 3 minutes, 4 seconds, 5 milliseconds') 74 | self.reader.timeshift(shift) 75 | expected_shifted_time_col = pd.DatetimeIndex(['2021-04-06 17:51:08.839000', '2021-04-06 17:51:08.845000', 76 | '2021-04-06 17:51:08.841000', '2021-04-06 17:51:08.847000', 77 | '2021-04-06 17:51:08.848000'], 78 | name='TimeStamp') 79 | pd.testing.assert_index_equal(self.reader.data.head().index, expected_shifted_time_col) 80 | 81 | def test_timeshift_to_timestamp(self): 82 | ts_shift = pd.Timestamp('01.02.2015, 11:56:32') 83 | self.reader.timeshift(ts_shift) 84 | expected_shifted_time_col = pd.DatetimeIndex(['2015-01-02 11:56:32', '2015-01-02 11:56:32.006000', 85 | '2015-01-02 11:56:32.002000', '2015-01-02 11:56:32.008000', 86 | '2015-01-02 11:56:32.009000'], 87 | name='TimeStamp') 88 | pd.testing.assert_index_equal(self.reader.data.head().index, expected_shifted_time_col) 89 | 90 | def test_random_timeshift(self): 91 | earliest_possible_time_col = pd.DatetimeIndex(['2019-04-06 15:48:04.834000', '2019-04-06 15:48:04.840000', 92 | '2019-04-06 15:48:04.836000', '2019-04-06 15:48:04.842000', 93 | '2019-04-06 15:48:04.843000']) 94 | 95 | latest_possible_time_col = pd.DatetimeIndex(['2021-03-06 15:48:04.834000', '2021-03-06 15:48:04.840000', 96 | '2021-03-06 15:48:04.836000', '2021-03-06 15:48:04.842000', 97 | '2021-03-06 15:48:04.843000']) 98 | 99 | self.reader.timeshift() 100 | self.assertTrue((earliest_possible_time_col <= self.reader.data.head().index).all()) 101 | self.assertTrue((self.reader.data.head().index <= latest_possible_time_col).all()) 102 | 103 | 104 | if __name__ == '__main__': 105 | unittest.main() 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pyOpenSci](https://tinyurl.com/y22nb8up)](https://github.com/pyOpenSci/software-review/issues/37) 2 | [![status](https://joss.theoj.org/papers/3abafc8a04e02d7c61d0bf4fb714af28/status.svg)](https://joss.theoj.org/papers/3abafc8a04e02d7c61d0bf4fb714af28) 3 | [![PyPI version](https://badge.fury.io/py/devicely.svg)](https://badge.fury.io/py/devicely) 4 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/devicely.svg)](https://pypi.python.org/pypi/devicely/) 5 | [![Actions Status: test](https://github.com/hpi-dhc/devicely/workflows/test/badge.svg)](https://github.com/hpi-dhc/devicely/actions/workflows/test.yml) 6 | ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/jostmorgenstern/270a0114dfad9251945a146dd6d29fa6/raw/devicely_coverage_main.json) 7 | [![DOI](https://zenodo.org/badge/279395106.svg)](https://zenodo.org/badge/latestdoi/279395106) 8 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 9 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/hpi-dhc/devicely-example/HEAD) 10 | 11 | ![Devicely Logo](https://github.com/hpi-dhc/devicely/blob/main/imgs/logo/devicely-logo.png) 12 | 13 | Devicely is a Python package for reading, de-identifying and writing data from various health monitoring sensors. 14 | With devicely, you can read sensor data and have it easily accessible in dataframes. 15 | You can also de-identify data and write them back using their original data format. This makes it convenient to share sensor data with other researchers while mantaining people's privacy. 16 | 17 | [Documentation](https://hpi-dhc.github.io/devicely/) 18 | 19 | [PyPi](https://pypi.org/project/devicely/) 20 | 21 | [Conda-forge](https://github.com/conda-forge/devicely-feedstock) 22 | 23 | ## Installation 24 | 25 | ### PyPi (current release) 26 | 27 | Installing `devicely` is as easy as executing: 28 | 29 | `pip install devicely` 30 | 31 | ### Conda-forge (current release) 32 | 33 | To install `devicely`through `conda-forge`: 34 | 35 | ``` 36 | conda config --add channels conda-forge 37 | conda config --set channel_priority strict 38 | ``` 39 | 40 | Once the `conda-forge` channel has been enabled, `devicely` can be installed with: 41 | 42 | `conda install devicely` 43 | 44 | List all of the versions of `devicely` available on your platform with: 45 | 46 | `conda search devicely --channel conda-forge` 47 | 48 | ### Locally (development version) 49 | 50 | ``` 51 | git clone git@github.com:hpi-dhc/devicely.git 52 | cd devicely 53 | pip install . 54 | ``` 55 | 56 | ## Sneak Peek 57 | 58 | All devices contain the following methods as exemplified through the `EmpaticaReader`: 59 | 60 | ``` 61 | empatica_reader = devicely.EmpaticaReader(path_to_empatica_files) 62 | empatica_reader.timeshift() 63 | empatica_reader.write(path_to_write_files) 64 | ``` 65 | 66 | You can also try this [notebook](https://github.com/hpi-dhc/devicely-example) 67 | with examples and sample data or check our binder: 68 | 69 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/hpi-dhc/devicely-example/HEAD) 70 | 71 | ## Quick Start 72 | 73 | To get started quickly, follow our [quick-start guide](https://hpi-dhc.github.io/devicely/examples.html#). 74 | 75 | Or check the full documentation: https://hpi-dhc.github.io/devicely/ 76 | 77 | 78 | ## Supported Sensors 79 | 80 | - [Empatica E4](https://e4.empatica.com/e4-wristband) is a wearable device that offers real-time physiological data acquisition such as blood volume pulse, electrodermal activity (EDA), heart rate, interbeat intervals, 3-axis acceleration and skin temperature. 81 | 82 | - [Biovotion Everion](https://www.biovotion.com/everion/) is a wearable device used for the continuous monitoring of vital signs. Currently, it measures the following vital signs: heart rate, blood pulse wave, heart rate variability, activity, SPO2, blood perfusion, respiration rate, steps, energy expenditure, skin temperature, EDA / galvanic skin response (GSR), barometric pressure and sleep. 83 | 84 | - [1-lead ECG monitor FarosTM 180 from Bittium](https://shop.bittium.com/product/36/bittium-faros-180-solution-pack) is a one channel ECG monitor with sampling frequency up to 1000 Hz and a 3D acceleration sampling up to 100Hz. 85 | 86 | - [Spacelabs (SL 90217)](https://www.spacelabshealthcare.com/products/diagnostic-cardiology/abp-monitoring/90217a/) is an oscillometric blood pressure (BP) monitor which can be used to automatically track a person's BP in specificed time intervals. 87 | 88 | - [TimeStamp for Android](https://play.google.com/store/apps/details?id=gj.timestamp&hl=en) allows you to record the timestamp of an event at the time it occurs. It also allows you to create specific tags such as "Running" or "Walking" and timestamp those specific activities. 89 | 90 | - [Shimmer Consensys GSR](https://www.shimmersensing.com/products/gsr-optical-pulse-development-kit#specifications-tab) is a device that is used to collect sensor data in real time and it contains sensors such as GSR / EDA, photoplethysmography (PPG), 3-axis accelerometer, 3-axis gyroscope, 3-axis magnetometer & integrated altimeter. 91 | 92 | - [Muse S headband](https://choosemuse.com/muse-s/) is a consumer grade headband consisting of 4 electrodes electroencephalography (EEG) sensors, 3-axis accelerometer (ACC), gyroscope, and photoplethysmography (PPG) sensors. 93 | 94 | ## How to Contribute 95 | 96 | If you want to be part of this mission, please check our documentation on how to contribute [here](https://hpi-dhc.github.io/devicely/contribution.html). 97 | 98 | ## Authors 99 | 100 | ``` 101 | * Ariane Sasso 102 | * Jost Morgenstern 103 | * Felix Musmann 104 | * Bert Arnrich 105 | ``` 106 | 107 | ## Contributors 108 | 109 | ``` 110 | * Arpita Kappattanavar 111 | * Bjarne Pfitzner 112 | * Lin Zhou 113 | * Pascal Hecker 114 | * Philipp Hildebrandt 115 | * Sidratul Moontaha 116 | ``` 117 | 118 | ## Citation 119 | 120 | Sasso, A., Morgenstern, J., Musmann, F., & Arnrich, B. (2021). Devicely: A Python package for reading, timeshifting and writing sensor data. Journal of Open Source Software, 6(66), 3679. https://doi.org/10.21105/joss.03679 121 | 122 | ### BibTeX 123 | ``` 124 | @article{Sasso2021, 125 | doi = {10.21105/joss.03679}, 126 | url = {https://doi.org/10.21105/joss.03679}, 127 | year = {2021}, 128 | publisher = {The Open Journal}, 129 | volume = {6}, 130 | number = {66}, 131 | pages = {3679}, 132 | author = {Ariane Sasso and Jost Morgenstern and Felix Musmann and Bert Arnrich}, 133 | title = {Devicely: A Python package for reading, timeshifting and writing sensor data}, 134 | journal = {Journal of Open Source Software} 135 | } 136 | ``` 137 | -------------------------------------------------------------------------------- /inst/paper.bib: -------------------------------------------------------------------------------- 1 | @article{Kamisalic2018, 2 | abstract = {Wearable devices have recently received considerable interest due to their great promise for a plethora of applications. Increased research efforts are oriented towards a non-invasive monitoring of human health as well as activity parameters. A wide range of wearable sensors are being developed for real-time non-invasive monitoring. This paper provides a comprehensive review of sensors used in wrist-wearable devices, methods used for the visualization of parameters measured as well as methods used for intelligent analysis of data obtained from wrist-wearable devices. In line with this, the main features of commercial wrist-wearable devices are presented. As a result of this review, a taxonomy of sensors, functionalities, and methods used in non-invasive wrist-wearable devices was assembled.}, 3 | author = {Kami\v{s}ali{\'{c}}, Aida and Fister, Iztok and Turkanovi{\'{c}}, Muhamed and Karakati\v{c}, Sa\v{s}o}, 4 | doi = {10.3390/s18061714}, 5 | issn = {14248220}, 6 | journal = {Sensors (Switzerland)}, 7 | keywords = {Intelligent analysis,Non-invasive,Sensor,Taxonomy,Visualization,Wrist-wearable device}, 8 | number = {6}, 9 | pmid = {29799504}, 10 | title = {{Sensors and functionalities of non-invasive wrist-wearable devices: A review}}, 11 | url = {www.mdpi.com/journal/sensors}, 12 | volume = {18}, 13 | year = {2018} 14 | } 15 | 16 | @misc{IDC2020, 17 | author = {IDC}, 18 | booktitle = {2020}, 19 | title = {{Shipments of Wearable Devices Leap to 125 Million Units, Up 35.1{\%} in the Third Quarter, According to IDC}}, 20 | url = {https://www.idc.com/getdoc.jsp?containerId=prUS47067820}, 21 | urldate = {2021-03-15}, 22 | year = {2020} 23 | } 24 | 25 | @article{Bayoumy2021, 26 | author = {Bayoumy, Karim and Gaber, Mohammed and Elshafeey, Abdallah and Mhaimeed, Omar and Dineen, Elizabeth H and Marvel, Francoise A and Martin, Seth S and Muse, Evan D and Turakhia, Mintu P and Tarakji, Khaldoun G and Elshazly, Mohamed B}, 27 | doi = {10.1038/s41569-021-00522-7}, 28 | isbn = {0123456789}, 29 | issn = {1759-5002}, 30 | journal = {Nature Reviews Cardiology}, 31 | month = {mar}, 32 | title = {{Smart wearable devices in cardiovascular care: where we are and how to move forward}}, 33 | url = {www.nature.com/nrcardio http://www.nature.com/articles/s41569-021-00522-7}, 34 | year = {2021} 35 | } 36 | 37 | @article{Mishra2020, 38 | abstract = {Consumer wearable devices that continuously measure vital signs have been used to monitor the onset of infectious disease. Here, we show that data from consumer smartwatches can be used for the pre-symptomatic detection of coronavirus disease 2019 (COVID-19). We analysed physiological and activity data from 32 individuals infected with COVID-19, identified from a cohort of nearly 5,300 participants, and found that 26 of them (81{\%}) had alterations in their heart rate, number of daily steps or time asleep. Of the 25 cases of COVID-19 with detected physiological alterations for which we had symptom information, 22 were detected before (or at) symptom onset, with four cases detected at least nine days earlier. Using retrospective smartwatch data, we show that 63{\%} of the COVID-19 cases could have been detected before symptom onset in real time via a two-tiered warning system based on the occurrence of extreme elevations in resting heart rate relative to the individual baseline. Our findings suggest that activity tracking and health monitoring via consumer wearable devices may be used for the large-scale, real-time detection of respiratory infections, often pre-symptomatically.}, 39 | author = {Mishra, Tejaswini and Wang, Meng and Metwally, Ahmed A and Bogu, Gireesh K and Brooks, Andrew W and Bahmani, Amir and Alavi, Arash and Celli, Alessandra and Higgs, Emily and Dagan-Rosenfeld, Orit and Fay, Bethany and Kirkpatrick, Susan and Kellogg, Ryan and Gibson, Michelle and Wang, Tao and Hunting, Erika M and Mamic, Petra and Ganz, Ariel B and Rolnik, Benjamin and Li, Xiao and Snyder, Michael P}, 40 | doi = {10.1038/s41551-020-00640-6}, 41 | issn = {2157846X}, 42 | journal = {Nature Biomedical Engineering}, 43 | number = {12}, 44 | pages = {1208--1220}, 45 | pmid = {33208926}, 46 | title = {{Pre-symptomatic detection of COVID-19 from smartwatch data}}, 47 | url = {https://doi.org/10.1038/s41551-020-00640-6}, 48 | volume = {4}, 49 | year = {2020} 50 | } 51 | 52 | @article{Snyder2020, 53 | doi = {10.21105/joss.02106}, 54 | url = {https://doi.org/10.21105/joss.02106}, 55 | year = {2020}, 56 | publisher = {The Open Journal}, 57 | volume = {5}, 58 | number = {47}, 59 | pages = {2106}, 60 | author = {Phil Snyder and Meghasyam Tummalacherla and Thanneer Perumal and Larsson Omberg}, 61 | title = {mhealthtools: A Modular R Package for Extracting Features from Mobile and Wearable Sensor Data}, 62 | journal = {Journal of Open Source Software} 63 | } 64 | 65 | @article{Christakis2019, 66 | doi = {10.21105/joss.01663}, 67 | url = {https://doi.org/10.21105/joss.01663}, 68 | year = {2019}, 69 | publisher = {The Open Journal}, 70 | volume = {4}, 71 | number = {44}, 72 | pages = {1663}, 73 | author = {Yiorgos Christakis and Nikhil Mahadevan and Shyamal Patel}, 74 | title = {SleepPy: A python package for sleep analysis from accelerometer data}, 75 | journal = {Journal of Open Source Software} 76 | } 77 | 78 | @article{Bartels2020, 79 | doi = {10.21105/joss.01867}, 80 | url = {https://doi.org/10.21105/joss.01867}, 81 | year = {2020}, 82 | publisher = {The Open Journal}, 83 | volume = {5}, 84 | number = {51}, 85 | pages = {1867}, 86 | author = {Rhenan Bartels and Tiago Peçanha}, 87 | title = {HRV: a Pythonic package for Heart Rate Variability Analysis}, 88 | journal = {Journal of Open Source Software} 89 | } 90 | 91 | @article{Czech2019, 92 | doi = {10.21105/joss.01778}, 93 | url = {https://doi.org/10.21105/joss.01778}, 94 | year = {2019}, 95 | publisher = {The Open Journal}, 96 | volume = {4}, 97 | number = {43}, 98 | pages = {1778}, 99 | author = {Matthew D. Czech and Shyamal Patel}, 100 | title = {GaitPy: An Open-Source Python Package for Gait Analysis Using an Accelerometer on the Lower Back}, 101 | journal = {Journal of Open Source Software} 102 | } 103 | 104 | @article{Foell2020, 105 | doi = {10.1016/j.cmpb.2021.106461}, 106 | author = {Föll, Simon and Maritsch, Martin and Spinola, Federica and Mishra, Varun and Barata, Filipe and Kowatsch, Tobias and Fleisch, Elgar and Wortmann, Felix}, 107 | title = {{{FLIRT}}: A {{Feature Generation Toolkit}} for {{Wearable Data}}}, 108 | journal = {Computer Methods and Programs in Biomedicine}, 109 | year = {2021} 110 | } 111 | 112 | @misc{Bent2020, 113 | author = {Bent, Brinnae}, 114 | publisher = {DBDP (Digital Biomarker Discovery Pipeline)}, 115 | title = {wearablecompute is an open source Python package containing over 50 data and domain-driven features}, 116 | url = {https://github.com/brinnaebent/wearablecompute}, 117 | year = {2020} 118 | } 119 | -------------------------------------------------------------------------------- /docs/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Python Module Index — devicely 1.1.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | 110 | 111 |
112 | 113 | 114 | 120 | 121 | 122 |
123 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 | 146 |
    147 | 148 |
  • »
  • 149 | 150 |
  • Python Module Index
  • 151 | 152 | 153 |
  • 154 | 155 |
  • 156 | 157 |
158 | 159 | 160 |
161 |
162 |
163 |
164 | 165 | 166 |

Python Module Index

167 | 168 |
169 | d 170 |
171 | 172 | 173 | 174 | 176 | 177 | 179 | 182 | 183 | 184 | 187 | 188 | 189 | 192 | 193 | 194 | 197 | 198 | 199 | 202 | 203 | 204 | 207 | 208 | 209 | 212 | 213 | 214 | 217 |
 
175 | d
180 | devicely 181 |
    185 | devicely.empatica 186 |
    190 | devicely.everion 191 |
    195 | devicely.faros 196 |
    200 | devicely.muse 201 |
    205 | devicely.shimmer_plus 206 |
    210 | devicely.spacelabs 211 |
    215 | devicely.time_stamp 216 |
218 | 219 | 220 |
221 | 222 |
223 |
224 | 225 |
226 | 227 |
228 |

229 | © Copyright 2021, Digital Health Center (Hasso Plattner Institute). 230 | 231 |

232 |
233 | 234 | 235 | 236 | Built with Sphinx using a 237 | 238 | theme 239 | 240 | provided by Read the Docs. 241 | 242 |
243 |
244 |
245 | 246 |
247 | 248 |
249 | 250 | 251 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /inst/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Devicely: A Python package for reading, timeshifting and writing sensor data' 3 | tags: 4 | - Python 5 | - Wearables 6 | - Sensors 7 | - Health 8 | authors: 9 | - name: Ariane Sasso^[corresponding author] 10 | orcid: 0000-0002-3669-4599 11 | affiliation: 1 12 | - name: Jost Morgenstern 13 | orcid: 0000-0001-6268-9948 14 | affiliation: 1 15 | - name: Felix Musmann 16 | orcid: 0000-0001-5365-0785 17 | affiliation: 1 18 | - name: Bert Arnrich 19 | orcid: 0000-0001-8380-7667 20 | affiliation: 1 21 | affiliations: 22 | - name: Digital Health Center, Hasso Plattner Institute, University of Potsdam 23 | index: 1 24 | date: 25 August 2021 25 | bibliography: paper.bib 26 | 27 | --- 28 | 29 | # Summary 30 | 31 | Wearable devices can track a multitude of parameters such as heart rate, body 32 | temperature, blood oxygen saturation, acceleration, blood glucose and many more 33 | [@Kamisalic2018]. Moreover, they are becoming increasingly popular with a 34 | steep increase in market presence in 2020 alone [@IDC2020]. Applications 35 | for wearable 36 | devices vary from tracking cardiovascular risks [@Bayoumy2021] to identifying 37 | COVID-19 onset [@Mishra2020]. Therefore, there is a great need for scientists to 38 | easily go through data acquired from different wearables and to be able to share 39 | them while protecting user privacy. 40 | In order to solve this problem and empower scientists working with biosignals, 41 | we developed the **devicely** package. It processes the data into a tabular 42 | format and contains tools for data de-identification. It allows scientists to focus 43 | on what they want: the analysis of biosignals guided by privacy principles. 44 | 45 | # Related Work 46 | 47 | The first example of a package working with wearable data is mhealthtools [@Snyder2020], which is developed in R and focuses on 48 | extracting features from sensors such as inertial measurement units (IMUs). Its main difference from **devicely** 49 | is firstly the language (R versus Python) and secondly their complementary 50 | nature. Mhealthtools offers functionalities for feature extraction and 51 | **devicely** is 52 | intended to help users in a prior step by reading and writing data from 53 | wearables into standardized formats. 54 | 55 | There are also packages developed in Python, such as SleepPy [@Christakis2019] which uses raw 56 | accelerometer data for assessing sleep quantity and quality. The HRV [@Bartels2020] package uses CSV and text files or Python iterables such as lists to generate 57 | features related to heart rate variability (HRV). GaitPy [@Czech2019] accepts input data in a 58 | customizable format and is mainly used to extract features for gait analysis. 59 | Therefore, packages such as SleepPy, HRV and GaitPy could also be used in a following step to extract features from the output generated by **devicely**. 60 | 61 | FLIRT [@Foell2020] and wearablecompute [@Bent2020] are packages that provide ways for 62 | reading data from specific wearables such as Empatica E4. They also 63 | include functionalities to extract features from electrodermal activity (EDA), 64 | acceleration and HRV. The main difference from FLIRT and wearablecompute to **devicely** is the 65 | focus on feature extraction versus privacy and data sharing. 66 | FLIRT and wearablecompute read the data for extracting features, while **devicely** aims to provide users with 67 | a way to read the data, de-identify them as necessary and write them back in a specified format. In 68 | this way, researchers can ensure even more data privacy and use the data easily 69 | for further analysis and sharing. 70 | 71 | # Statement of need 72 | 73 | Every wearable vendor has a different data format and reading them is 74 | usually a challenge for scientists. Therefore, in order for researchers to be able to use different sensor data in an easy and 75 | friendly way we developed the **devicely** package. The package also contains two methods to help with data de-identification, one 76 | is called timeshift and the other is a write method. The idea behind them is 77 | that researchers can timeshift all their time series data to a different time from the one the 78 | actual experiments occurred and then write this new de-identified dataset back to 79 | the original or a similar data format. This will empower scientists to maintain user privacy 80 | and hopefully share more data to increase research reproducibility. 81 | 82 | # Design 83 | 84 | Different wearables provide incompatible data formats which require specific preprocessing steps. 85 | However, it should be easy for scientists to add data from a new wearable to an 86 | existing pipeline and easy for developers to add a new wearable to the 87 | **devicely** package. To achieve both, **devicely** encapsulates data preparation for 88 | each wearable behind three common methods: _read_, _timeshift_ and _write_. 89 | 90 | After reading, the data is accessible through the reader in common formats such as Pandas DataFrames. 91 | De-identification is achieved by timeshifting the data, either by providing a shifting interval or randomly. 92 | For writing back de-identified data, **devicely** focuses on keeping a format that can be read again using the same reader class. 93 | In almost all cases, this is the same format as the one the wearable originaly provides. 94 | This enables sharing data with the community while maintaining user privacy. 95 | 96 | # Functionalities 97 | 98 | All reader classes support three core functions: reading data created by a wearable, timeshifting them and writing them back. 99 | To _read_ data the corresponding reader class can be initialized using as a 100 | parameter a path to the data created by the wearable. After reading, data can 101 | be accessed through the reader in convenient formats such as dictionaries 102 | and Pandas DataFrames. 103 | 104 | After creating a reader object the method _timeshift_ can be applied upon it. This assures de-identification by shifting all time-related data points. 105 | To control the shifting interval, a parameter can be provided to _timeshift_. 106 | If no parameter is provided, the data is shifted by a random time interval to the past. 107 | The timeshifted data can be written back using the _write_ method. 108 | 109 | For all wearables, the written data can be read again using the same reader 110 | class. \autoref{fig:devicely_structure} depicts the class structure of the **devicely** 111 | package and serves as a guide for future implementation. 112 | 113 | ![On the left side, the structure of the files in the **devicely** package is 114 | depicted. Each device should have a separate file. On the right side at the top 115 | we show how to import a class from a device file into \_\_init\_\_.py. At the right bottom side there is 116 | an example of one device class and its methods.\label{fig:devicely_structure}](devicely_structure.png) 117 | 118 | # Availability 119 | 120 | The software can be obtained through the [Python Package Index 121 | (PyPI)](https://pypi.org/project/devicely), 122 | [Conda-forge](https://github.com/conda-forge/devicely-feedstock), [Zenodo](https://zenodo.org/record/5243494) and on 123 | [GitHub](https://github.com/hpi-dhc/devicely) under the MIT License. 124 | 125 | # Mention 126 | 127 | This package was used in the following paper: 128 | 129 | Morassi Sasso A. et al. (2020) HYPE: Predicting Blood Pressure from 130 | Photoplethysmograms in a Hypertensive Population. In: Michalowski M., Moskovitch 131 | R. (eds) Artificial Intelligence in Medicine. AIME 2020. Lecture Notes in 132 | Computer Science, vol 12299. Springer, Cham. 133 | https://doi.org/10.1007/978-3-030-59137-3_29 134 | 135 | [GitHub Repository](https://github.com/arianesasso/aime-2020) 136 | 137 | # Acknowledgements 138 | 139 | We acknowledge contributions from Arpita Kappattanavar, Bjarne Pfitzner, Harry Freitas da Cruz, Lin 140 | Zhou, Pascal Hecker, Philipp Hildebrandt and Sidratul Moontaha during the genesis and testing of this package. 141 | 142 | # References 143 | -------------------------------------------------------------------------------- /tests/test_shimmer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the ShimmerPlus module 3 | """ 4 | import os 5 | import sys 6 | import unittest 7 | 8 | import pandas as pd 9 | import devicely 10 | 11 | 12 | class ShimmerPlusTestCase(unittest.TestCase): 13 | READ_PATH = 'tests/Shimmer_test_data/read_test_data.csv' 14 | WRITE_PATH = 'tests/Shimmer_test_data/write_test_data.csv' 15 | 16 | def setUp(self): 17 | self.expected_delimiter = ';' 18 | 19 | self.expected_data_head = pd.DataFrame( 20 | {'Shimmer_40AC_Timestamp_Unix_CAL': pd.to_datetime(['2020-07-28 10:56:50.034000', '2020-07-28 10:56:50.057000', '2020-07-28 10:56:50.074000', '2020-07-28 10:56:50.099000', '2020-07-28 10:56:50.111000']), 21 | 'Shimmer_40AC_Accel_LN_X_CAL': [-1.434782608695652, -1.4021739130434783, -1.434782608695652, -1.4130434782608696, -1.4456521739130435], 22 | 'Shimmer_40AC_Accel_LN_Y_CAL': [10.0, 10.0, 10.0, 10.0, 10.0], 23 | 'Shimmer_40AC_Accel_LN_Z_CAL': [0.5543478260869565, 0.5543478260869565, 0.5543478260869565, 0.5217391304347826, 0.5108695652173912], 24 | 'Shimmer_40AC_Accel_WR_X_CAL': [-3.9305804907241173, -3.9233991621783364, -3.897067624177139, -3.932974266906044, -3.944943147815679], 25 | 'Shimmer_40AC_Accel_WR_Y_CAL': [8.42130460801915, 8.442848593656493, 8.42848593656493, 8.42130460801915, 8.42848593656493], 26 | 'Shimmer_40AC_Accel_WR_Z_CAL': [-1.620586475164572, -1.5990424895272293, -1.6038300418910831, -1.589467384799521, -1.6612806702573308], 27 | 'Shimmer_40AC_Battery_CAL': [4139.194139194139, 4137.728937728937, 4111.355311355311, 4140.65934065934, 4134.798534798534], 28 | 'Shimmer_40AC_Ext_Exp_A15_CAL': [1684.9816849816848, 1673.260073260073, 1901.098901098901, 1722.3443223443223, 1678.3882783882782], 29 | 'Shimmer_40AC_GSR_Range_CAL': [2.0, 2.0, 2.0, 2.0, 2.0], 30 | 'Shimmer_40AC_GSR_Skin_Conductance_CAL': [2.493040293040293, 2.4915750915750916, 2.49010989010989, 2.4886446886446887, 2.4886446886446887], 31 | 'Shimmer_40AC_GSR_Skin_Resistance_CAL': [401.1166617690273, 401.3525433695972, 401.58870255957635, 401.8251398292611, 401.8251398292611], 32 | 'Shimmer_40AC_Gyro_X_CAL': [0.13740458015267176, 0.183206106870229, 0.2748091603053435, 0.22900763358778625, 0.13740458015267176], 33 | 'Shimmer_40AC_Gyro_Y_CAL': [1.8778625954198473, 1.3282442748091603, 1.282442748091603, 1.450381679389313, 1.5114503816793892], 34 | 'Shimmer_40AC_Gyro_Z_CAL': [-0.183206106870229, -0.41221374045801523, -0.1984732824427481, -0.12213740458015267, -0.47328244274809156], 35 | 'Shimmer_40AC_Int_Exp_A12_CAL': [1680.5860805860805, 1703.296703296703, 1687.179487179487, 1650.5494505494505, 1701.8315018315018], 36 | 'Shimmer_40AC_Mag_X_CAL': [-0.11244377811094453, -0.10944527736131934, -0.10794602698650674, -0.10644677661169415, -0.11394302848575712], 37 | 'Shimmer_40AC_Mag_Y_CAL': [-0.9160419790104948, -0.9130434782608695, -0.9100449775112444, -0.9010494752623688, -0.9040479760119939], 38 | 'Shimmer_40AC_Mag_Z_CAL': [-0.047976011994003, -0.047976011994003, -0.04947526236881559, -0.050974512743628186, -0.037481259370314844], 39 | 'Shimmer_40AC_Pressure_BMP280_CAL': [100.43537920858897, 100.4297311006671, 100.44102732749235, 100.44102732749235, 100.43820326666797], 40 | 'Shimmer_40AC_Temperature_BMP280_CAL': [33.365877509029815, 33.365877509029815, 33.365877509029815, 33.365877509029815, 33.365877509029815], 41 | 'Shimmer_40AC_Accel_LN_mag': [10.1176036 , 10.11303086, 10.1176036 , 10.11280889, 10.11686206]}, 42 | ) 43 | 44 | self.expected_units = { 45 | 'Shimmer_40AC_Timestamp_Unix_CAL': 'ms', 46 | 'Shimmer_40AC_Accel_LN_X_CAL': 'm/(s^2)', 47 | 'Shimmer_40AC_Accel_LN_Y_CAL': 'm/(s^2)', 48 | 'Shimmer_40AC_Accel_LN_Z_CAL': 'm/(s^2)', 49 | 'Shimmer_40AC_Accel_WR_X_CAL': 'm/(s^2)', 50 | 'Shimmer_40AC_Accel_WR_Y_CAL': 'm/(s^2)', 51 | 'Shimmer_40AC_Accel_WR_Z_CAL': 'm/(s^2)', 52 | 'Shimmer_40AC_Battery_CAL': 'mV', 53 | 'Shimmer_40AC_Ext_Exp_A15_CAL': 'mV', 54 | 'Shimmer_40AC_GSR_Range_CAL': 'no_units', 55 | 'Shimmer_40AC_GSR_Skin_Conductance_CAL': 'uS', 56 | 'Shimmer_40AC_GSR_Skin_Resistance_CAL': 'kOhms', 57 | 'Shimmer_40AC_Gyro_X_CAL': 'deg/s', 58 | 'Shimmer_40AC_Gyro_Y_CAL': 'deg/s', 59 | 'Shimmer_40AC_Gyro_Z_CAL': 'deg/s', 60 | 'Shimmer_40AC_Int_Exp_A12_CAL': 'mV', 61 | 'Shimmer_40AC_Mag_X_CAL': 'local_flux', 62 | 'Shimmer_40AC_Mag_Y_CAL': 'local_flux', 63 | 'Shimmer_40AC_Mag_Z_CAL': 'local_flux', 64 | 'Shimmer_40AC_Pressure_BMP280_CAL': 'kPa', 65 | 'Shimmer_40AC_Temperature_BMP280_CAL': 'Degrees Celsius' 66 | } 67 | 68 | self.reader = devicely.ShimmerPlusReader(self.READ_PATH) 69 | 70 | def _compare_reader_and_expected_values(self, reader, expected_delimiter, expected_units, expected_data_head): 71 | self.assertEqual(reader.delimiter, expected_delimiter) 72 | self.assertEqual(reader.units, expected_units) 73 | pd.testing.assert_frame_equal(reader.data.head(), expected_data_head) 74 | 75 | def test_basic_read(self): 76 | self._compare_reader_and_expected_values(self.reader, 77 | self.expected_delimiter, 78 | self.expected_units, 79 | self.expected_data_head) 80 | 81 | def test_write(self): 82 | self.reader.write(self.WRITE_PATH) 83 | new_reader = devicely.ShimmerPlusReader(self.WRITE_PATH) 84 | self._compare_reader_and_expected_values(new_reader, 85 | self.reader.delimiter, 86 | self.reader.units, 87 | self.reader.data.head()) 88 | os.remove(self.WRITE_PATH) 89 | 90 | def test_timeshift_by_timedelta(self): 91 | shift = pd.Timedelta('1 days, 2 hours, 3 minutes, 4 seconds, 5 milliseconds') 92 | self.reader.timeshift(shift) 93 | expected_shifted_time_col = pd.Series(pd.to_datetime(['2020-07-29 12:59:54.039000', '2020-07-29 12:59:54.062000', 94 | '2020-07-29 12:59:54.079000', '2020-07-29 12:59:54.104000', 95 | '2020-07-29 12:59:54.116000'])) 96 | pd.testing.assert_series_equal(self.reader.data.loc[:4, 'Shimmer_40AC_Timestamp_Unix_CAL'], expected_shifted_time_col, check_names=False) 97 | 98 | def test_timeshift_to_timestamp(self): 99 | shift = pd.Timestamp(day=23, month=4, year=2009, hour=6, minute=42, second=21, microsecond=int(593.32e3)) 100 | self.reader.timeshift(shift) 101 | expected_shifted_time_col = pd.Series(pd.to_datetime(['2009-04-23 06:42:21.593000', '2009-04-23 06:42:21.616000', 102 | '2009-04-23 06:42:21.633000', '2009-04-23 06:42:21.658000', 103 | '2009-04-23 06:42:21.670000'])) 104 | pd.testing.assert_series_equal(self.reader.data.loc[:4, 'Shimmer_40AC_Timestamp_Unix_CAL'], expected_shifted_time_col, check_names=False) 105 | 106 | def test_random_timeshift(self): 107 | earliest_possible_time_col = pd.Series(pd.to_datetime(['2018-07-29 10:56:50.034000', '2018-07-29 10:56:50.057000', 108 | '2018-07-29 10:56:50.074000', '2018-07-29 10:56:50.099000', 109 | '2018-07-29 10:56:50.111000'])) 110 | latest_possible_time_col = pd.Series(pd.to_datetime(['2020-06-28 10:56:50.034000', '2020-06-28 10:56:50.057000', 111 | '2020-06-28 10:56:50.074000', '2020-06-28 10:56:50.099000', 112 | '2020-06-28 10:56:50.111000'])) 113 | 114 | self.reader.timeshift() 115 | self.assertTrue((earliest_possible_time_col <= self.reader.data.loc[:4, 'Shimmer_40AC_Timestamp_Unix_CAL']).all()) 116 | self.assertTrue((self.reader.data.loc[:4, 'Shimmer_40AC_Timestamp_Unix_CAL'] <= latest_possible_time_col).all()) 117 | 118 | 119 | if __name__ == '__main__': 120 | unittest.main() 121 | -------------------------------------------------------------------------------- /tests/test_spacelabs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the Spacelabs module 3 | """ 4 | import datetime as dt 5 | import os 6 | import unittest 7 | 8 | import numpy as np 9 | import pandas as pd 10 | 11 | import devicely 12 | 13 | 14 | class SpacelabsTestCase(unittest.TestCase): 15 | READ_PATH = "tests/SpaceLabs_test_data/spacelabs.abp" 16 | READ_PATH_REPEATED = "tests/SpaceLabs_test_data/spacelabs_repeated.abp" 17 | WRITE_PATH = "tests/SpaceLabs_test_data/spacelabs_written.abp" 18 | 19 | def __init__(self, *args, **kwargs): 20 | super().__init__(*args, **kwargs) 21 | 22 | self.expected_subject = "000002" 23 | timestamps = pd.to_datetime([ 24 | "1.1.99 17:03", 25 | "1.1.99 17:05", 26 | "1.1.99 17:07", 27 | "1.1.99 17:09", 28 | "1.1.99 17:11", 29 | "1.1.99 17:13", 30 | "1.1.99 17:25", 31 | "1.1.99 17:28", 32 | "1.1.99 17:31", 33 | "1.1.99 17:34", 34 | "1.1.99 17:36", 35 | "1.1.99 17:39", 36 | "1.1.99 23:42", 37 | "1.1.99 23:59", 38 | "1.2.99 00:01", 39 | ]) 40 | 41 | timestamps_repeated = pd.to_datetime([ 42 | "1.1.99 17:05", 43 | "1.1.99 17:05", 44 | "1.1.99 17:07", 45 | "1.1.99 17:09", 46 | "1.1.99 17:11", 47 | "1.1.99 17:13", 48 | "1.1.99 17:25", 49 | "1.1.99 17:28", 50 | "1.1.99 17:31", 51 | "1.1.99 17:34", 52 | "1.1.99 17:36", 53 | "1.1.99 17:39", 54 | "1.1.99 23:42", 55 | "1.1.99 23:59", 56 | "1.2.99 00:01", 57 | ]) 58 | 59 | self.expected_data = pd.DataFrame({ 60 | "timestamp": timestamps, 61 | "date": timestamps.map(lambda timestamp: timestamp.date()), 62 | "time": timestamps.map(lambda timestamp: timestamp.time()), 63 | "SYS(mmHg)": [11, 142, 152, 151, 145, 3, 4, 164, 154, 149, 153, 148, 148, 148, 148], 64 | "DIA(mmHg)": [0, 118, 112, 115, 110, 0, 0, 119, 116, 119, 118, 114, 114, 114, 114], 65 | "UNKNOW_1": [0, 99, 95, 96, 91, 0, 0, 95, 95, 98, 96, 93, 93, 93, 93], 66 | "UNKNOW_2": [0, 61, 61, 61, 59, 0, 0, 63, 63, 63, 60, 62, 62, 62, 62], 67 | "UNKNOW_3": 15 * [np.nan], 68 | "CODE": ["EB", np.nan, np.nan, np.nan, np.nan, "EB", "EB", np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], 69 | }) 70 | 71 | self.expected_data.set_index('timestamp', inplace=True) 72 | 73 | self.expected_data_repeated = pd.DataFrame({ 74 | "timestamp": timestamps_repeated, 75 | "date": timestamps_repeated.map(lambda timestamps_repeated: timestamps_repeated.date()), 76 | "time": timestamps_repeated.map(lambda timestamps_repeated: timestamps_repeated.time()), 77 | "SYS(mmHg)": [11, 142, 152, 151, 145, 3, 4, 164, 154, 149, 153, 148, 148, 148, 148], 78 | "DIA(mmHg)": [0, 118, 112, 115, 110, 0, 0, 119, 116, 119, 118, 114, 114, 114, 114], 79 | "UNKNOW_1": [0, 99, 95, 96, 91, 0, 0, 95, 95, 98, 96, 93, 93, 93, 93], 80 | "UNKNOW_2": [0, 61, 61, 61, 59, 0, 0, 63, 63, 63, 60, 62, 62, 62, 62], 81 | "UNKNOW_3": 15 * [np.nan], 82 | "CODE": ["EB", np.nan, np.nan, np.nan, np.nan, "EB", "EB", np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], 83 | }) 84 | 85 | self.expected_data_repeated.set_index('timestamp', inplace=True) 86 | 87 | self.expected_metadata = { 88 | "PATIENTINFO": { 89 | "DOB": "16.09.1966", 90 | "RACE": "native american" 91 | }, 92 | "REPORTINFO": { 93 | "PHYSICIAN": "Dr. Hannibal Lecter", 94 | "NURSETECH": "admin", 95 | "STATUS": "NOTCONFIRMED", 96 | "CALIPERSUMMARY": { 97 | "COUNT": "0" 98 | }, 99 | }, 100 | } 101 | 102 | def setUp(self): 103 | self.spacelabs_reader = devicely.SpacelabsReader(self.READ_PATH) 104 | self.spacelabs_reader_repertion = devicely.SpacelabsReader(self.READ_PATH_REPEATED) 105 | 106 | def test_read(self): 107 | # Tests a basic reading operation. 108 | pd.testing.assert_frame_equal(self.spacelabs_reader.data, 109 | self.expected_data) 110 | 111 | # Test reading a file with repeated timestamp values. 112 | pd.testing.assert_frame_equal(self.spacelabs_reader_repertion.data, 113 | self.expected_data_repeated) 114 | 115 | self.assertEqual(self.spacelabs_reader.subject, self.expected_subject) 116 | self.assertEqual(self.spacelabs_reader.metadata, 117 | self.expected_metadata) 118 | 119 | def test_deidentify(self): 120 | # Tests if the SpacelabsReader.deidentify method removes all patient metadata. 121 | 122 | self.spacelabs_reader.deidentify() 123 | 124 | self.assertEqual(self.spacelabs_reader.subject, "") 125 | self.assertEqual( 126 | self.spacelabs_reader.metadata, 127 | { 128 | "PATIENTINFO": { 129 | "DOB": "", 130 | "RACE": "" 131 | }, 132 | "REPORTINFO": { 133 | "PHYSICIAN": "", 134 | "NURSETECH": "", 135 | "STATUS": "", 136 | "CALIPERSUMMARY": { 137 | "COUNT": "" 138 | }, 139 | }, 140 | }, 141 | ) 142 | 143 | def test_write(self): 144 | # Tests the SpacelabsReader.write operation by writing, reading again and comparing the old and new signals. 145 | 146 | self.spacelabs_reader.write(self.WRITE_PATH) 147 | new_reader = devicely.SpacelabsReader(self.WRITE_PATH) 148 | 149 | pd.testing.assert_frame_equal(new_reader.data, 150 | self.spacelabs_reader.data) 151 | self.assertEqual(new_reader.metadata, self.spacelabs_reader.metadata) 152 | self.assertEqual(new_reader.subject, self.spacelabs_reader.subject) 153 | 154 | os.remove(self.WRITE_PATH) 155 | 156 | def test_random_timeshift(self): 157 | earliest_possible_shifted_time_col = pd.to_datetime([ 158 | '1997-01-01 17:03:00', 159 | '1997-01-01 17:05:00', 160 | '1997-01-01 17:07:00', 161 | '1997-01-01 17:09:00', 162 | '1997-01-01 17:11:00', 163 | '1997-01-01 17:13:00', 164 | '1997-01-01 17:25:00', 165 | '1997-01-01 17:28:00', 166 | '1997-01-01 17:31:00', 167 | '1997-01-01 17:34:00', 168 | '1997-01-01 17:36:00', 169 | '1997-01-01 17:39:00', 170 | '1997-01-01 23:42:00', 171 | '1997-01-01 23:59:00', 172 | '1997-01-02 00:01:00' 173 | ]) 174 | latest_possible_shifted_time_col = pd.to_datetime([ 175 | '1998-12-02 17:03:00', 176 | '1998-12-02 17:05:00', 177 | '1998-12-02 17:07:00', 178 | '1998-12-02 17:09:00', 179 | '1998-12-02 17:11:00', 180 | '1998-12-02 17:13:00', 181 | '1998-12-02 17:25:00', 182 | '1998-12-02 17:28:00', 183 | '1998-12-02 17:31:00', 184 | '1998-12-02 17:34:00', 185 | '1998-12-02 17:36:00', 186 | '1998-12-02 17:39:00', 187 | '1998-12-02 23:42:00', 188 | '1998-12-02 23:59:00', 189 | '1998-12-03 00:01:00' 190 | ]) 191 | 192 | self.spacelabs_reader.timeshift() 193 | new_timestamp_column = pd.Series(self.spacelabs_reader.data.index) 194 | 195 | self.assertTrue((earliest_possible_shifted_time_col <= new_timestamp_column).all()) 196 | self.assertTrue((new_timestamp_column <= latest_possible_shifted_time_col).all()) 197 | 198 | new_date_column = self.spacelabs_reader.data["date"] 199 | new_time_column = self.spacelabs_reader.data["time"] 200 | testing_timestamp_column = pd.Series([ 201 | dt.datetime.combine(new_date_column[i], new_time_column[i]) 202 | for i in range(len(self.spacelabs_reader.data)) 203 | ]) 204 | 205 | pd.testing.assert_series_equal(new_timestamp_column, testing_timestamp_column, check_names=False) 206 | 207 | if __name__ == "__main__": 208 | unittest.main() 209 | -------------------------------------------------------------------------------- /devicely/spacelabs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Spacelabs (SL 90217) is an oscillometric blood pressure (BP) monitor which can be used to 3 | automatically track a person's BP in specificed time intervals. 4 | 5 | """ 6 | import csv 7 | import datetime as dt 8 | import random 9 | from xml.etree import ElementTree as ET 10 | 11 | import pandas as pd 12 | 13 | 14 | class SpacelabsReader: 15 | """ 16 | Read, timeshift, deidentify and write data 17 | generated by Spacelabs(SL90217). 18 | 19 | Attributes 20 | ---------- 21 | data : DataFrame 22 | DataFrame with the values that were read from the abp file. 23 | 24 | subject : str 25 | Contain the sujbect's id. Can be changed for 26 | deidentification. 27 | 28 | valid_measurements : str 29 | Contain the number of valid measurements in the 30 | abp file. 31 | 32 | metadata : dict 33 | The measurements' metadata. Read from the xml at the bottom 34 | of the abp file. Can be erased for deidentification. 35 | """ 36 | 37 | def __init__(self, path): 38 | """ 39 | Reads the abp file generated by the Spacelabs device and saves the 40 | parsed DataFrame. 41 | 42 | Parameters 43 | ---------- 44 | path : str 45 | Path of the abp file. 46 | """ 47 | 48 | # Metadata Definition 49 | metadata = pd.read_csv(path, nrows=5, header=None) 50 | self.subject = str(metadata.loc[0, 0]) 51 | base_date = dt.datetime.strptime(metadata.loc[2, 0], '%d.%m.%Y').date() 52 | if metadata.loc[4, 0] != 'Unknown Line': 53 | self.valid_measurements = str(metadata.loc[4, 0]) 54 | else: 55 | metadata = pd.read_csv(path, nrows=6, header=None) 56 | self.valid_measurements = str(metadata.loc[5, 0]) 57 | 58 | column_names = ['hour', 'minutes', 'SYS(mmHg)', 'DIA(mmHg)', 'UNKNOW_1', 'UNKNOW_2', 'CODE', 'UNKNOW_3'] 59 | self.data = pd.read_csv(path, sep=',', skiprows=51, skipfooter=1, header=None, names=column_names, engine='python') 60 | 61 | # Adjusting Date 62 | dates = [base_date] 63 | times = [dt.time(hour=self.data.loc[i, 'hour'], minute=self.data.loc[i, 'minutes']) for i in range(len(self.data))] 64 | current_date = base_date 65 | for i in range(1, len(times)): 66 | if times[i] < times[i-1]: 67 | current_date += dt.timedelta(days=1) 68 | dates.append(current_date) 69 | 70 | self.data.reset_index(inplace=True) 71 | self.data['timestamp'] = pd.to_datetime([dt.datetime.combine(dates[i], times[i]) for i in range(len(dates))]) 72 | self.data['date'] = dates 73 | self.data['time'] = times 74 | 75 | order = ['timestamp', 'date', 'time', 'SYS(mmHg)', 'DIA(mmHg)', 'UNKNOW_1', 'UNKNOW_2', 'UNKNOW_3', 'CODE'] 76 | self.data = self.data[order] 77 | 78 | try: 79 | self.data.set_index('timestamp', inplace=True) 80 | except KeyError: 81 | print('Timestamp can not be set as an index:') 82 | print(KeyError) 83 | 84 | xml_line = open(path, 'r').readlines()[-1] 85 | xml_root = ET.fromstring(xml_line) 86 | self.metadata = self._etree_to_dict(xml_root)['XML'] 87 | 88 | 89 | def deidentify(self, subject_id=None): 90 | """ 91 | Deidentify the data by removing the original XML metadata and 92 | subject id. 93 | 94 | Parameters 95 | ---------- 96 | subject_id : str, optional 97 | New subject id to be written in the 98 | deidentified file, by default None. 99 | """ 100 | # Changing subject id 101 | if subject_id: 102 | self.subject = subject_id 103 | else: 104 | self.subject = '' 105 | 106 | self.metadata = { 107 | 'PATIENTINFO' : {'DOB' : '', 108 | 'RACE' : ''}, 109 | 'REPORTINFO' : {'PHYSICIAN' : '', 110 | 'NURSETECH' : '', 111 | 'STATUS' : '', 112 | 'CALIPERSUMMARY' : {'COUNT' : ''}} 113 | } 114 | 115 | def write(self, path): 116 | """ 117 | Write the signals and metadata to the 118 | writing path in the same format as it was read. 119 | 120 | Parameters 121 | ---------- 122 | path : str 123 | Path to writing file. Writing mode: 'w'. 124 | Use the file extension 'abp' to keep the SpaceLabs standard. 125 | """ 126 | 127 | with open(path, 'w') as file: 128 | file.write(f"\n{self.subject}") 129 | file.write(8 * '\n') 130 | file.write("0") 131 | file.write(8 * '\n') 132 | file.write(self.data.date[0].strftime("%d.%m.%Y")) 133 | file.write(7 * '\n') 134 | file.write("Unknown Line") 135 | file.write(26 * '\n') 136 | file.write(self.valid_measurements + "\n") 137 | printing_df = self.data.drop(columns=['date', 'time']) 138 | printing_df['hours'] = self.data.time.map(lambda x: x.strftime("%H")) 139 | printing_df['minutes'] = self.data.time.map(lambda x: x.strftime("%M")) 140 | order = ['hours', 'minutes', 'SYS(mmHg)', 'DIA(mmHg)', 'UNKNOW_1', 'UNKNOW_2', 'CODE', 'UNKNOW_3'] 141 | printing_df = printing_df[order] 142 | printing_df.fillna(-9999, inplace=True) 143 | printing_df.replace('EB', -9998, inplace=True) 144 | printing_df.replace('AB', -9997, inplace=True) 145 | printing_df[['SYS(mmHg)', 'DIA(mmHg)', 'UNKNOW_1', 'UNKNOW_2', 'CODE', 'UNKNOW_3']] = printing_df[ 146 | ['SYS(mmHg)', 'DIA(mmHg)', 'UNKNOW_1', 'UNKNOW_2', 'CODE', 'UNKNOW_3']].astype(int).astype(str) 147 | printing_df.replace('-9999', '""', inplace=True) 148 | printing_df.replace('-9998', '"EB"', inplace=True) 149 | printing_df.replace('-9997', '"AB"', inplace=True) 150 | printing_df.to_csv(file, header=None, index=None, quoting=csv.QUOTE_NONE, line_terminator='\n') 151 | 152 | xml_node = ET.Element('XML') 153 | xml_node.extend(self._dict_to_etree(self.metadata)) 154 | xml_line = ET.tostring(xml_node, encoding="unicode") 155 | file.write(xml_line) 156 | 157 | def _etree_to_dict(self, etree_node): 158 | children = list(iter(etree_node)) 159 | if len(children) == 0: 160 | return {etree_node.tag: etree_node.text} 161 | else: 162 | dict_ = dict() 163 | for child in children: 164 | dict_ = {**dict_, **self._etree_to_dict(child)} 165 | return {etree_node.tag: dict_} 166 | 167 | def _dict_to_etree(self, dict_): 168 | def rec(key, value): 169 | node = ET.Element(key) 170 | if isinstance(value, dict): 171 | for child_key in value: 172 | node.append(rec(child_key, value[child_key])) 173 | else: 174 | node.text = str(value) if value else '' 175 | return node 176 | 177 | return [rec(k, v) for k, v in dict_.items()] 178 | 179 | def timeshift(self, shift='random'): 180 | """ 181 | Timeshift the data by shifting all time related columns. 182 | 183 | Parameters 184 | ---------- 185 | shift : None/'random', pd.Timestamp or pd.Timedelta 186 | If shift is not specified, shifts the data by a random time interval 187 | between one month and two years to the past. 188 | 189 | If shift is a timdelta, shifts the data by that timedelta. 190 | 191 | If shift is a timestamp, shifts the data such that the earliest entry 192 | has that timestamp. The remaining values will mantain the same 193 | time difference to the first entry. 194 | """ 195 | 196 | if shift == 'random': 197 | one_month = pd.Timedelta('30 days').value 198 | two_years = pd.Timedelta('730 days').value 199 | random_timedelta = - pd.Timedelta(random.uniform(one_month, two_years)).round('min') 200 | self.timeshift(random_timedelta) 201 | 202 | if not self.data.index.empty: 203 | if isinstance(shift, pd.Timestamp): 204 | timedeltas = self.data.index - self.data.index[0] 205 | self.data.index = shift.round('min') + timedeltas 206 | if isinstance(shift, pd.Timedelta): 207 | self.data.index += shift.round('min') 208 | self.data['date'] = self.data.index.map(lambda timestamp: timestamp.date()) 209 | self.data['time'] = self.data.index.map(lambda timestamp: timestamp.time()) 210 | else: 211 | if isinstance(shift, pd.Timestamp): 212 | timedeltas = self.data['timestamp'] - self.data['timestamp'].min() 213 | self.data['timestamp'] = shift.round('min') + timedeltas 214 | if isinstance(shift, pd.Timedelta): 215 | self.data['timestamp'] += shift.round('min') 216 | self.data['date'] = self.data['timestamp'].map(lambda timestamp: timestamp.date()) 217 | self.data['time'] = self.data['timestamp'].map(lambda timestamp: timestamp.time()) 218 | -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | * 33 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 34 | */ 35 | jQuery.urldecode = function(x) { 36 | if (!x) { 37 | return x 38 | } 39 | return decodeURIComponent(x.replace(/\+/g, ' ')); 40 | }; 41 | 42 | /** 43 | * small helper function to urlencode strings 44 | */ 45 | jQuery.urlencode = encodeURIComponent; 46 | 47 | /** 48 | * This function returns the parsed url parameters of the 49 | * current request. Multiple values per key are supported, 50 | * it will always return arrays of strings for the value parts. 51 | */ 52 | jQuery.getQueryParameters = function(s) { 53 | if (typeof s === 'undefined') 54 | s = document.location.search; 55 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 56 | var result = {}; 57 | for (var i = 0; i < parts.length; i++) { 58 | var tmp = parts[i].split('=', 2); 59 | var key = jQuery.urldecode(tmp[0]); 60 | var value = jQuery.urldecode(tmp[1]); 61 | if (key in result) 62 | result[key].push(value); 63 | else 64 | result[key] = [value]; 65 | } 66 | return result; 67 | }; 68 | 69 | /** 70 | * highlight a given string on a jquery object by wrapping it in 71 | * span elements with the given class name. 72 | */ 73 | jQuery.fn.highlightText = function(text, className) { 74 | function highlight(node, addItems) { 75 | if (node.nodeType === 3) { 76 | var val = node.nodeValue; 77 | var pos = val.toLowerCase().indexOf(text); 78 | if (pos >= 0 && 79 | !jQuery(node.parentNode).hasClass(className) && 80 | !jQuery(node.parentNode).hasClass("nohighlight")) { 81 | var span; 82 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 83 | if (isInSVG) { 84 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 85 | } else { 86 | span = document.createElement("span"); 87 | span.className = className; 88 | } 89 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 90 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 91 | document.createTextNode(val.substr(pos + text.length)), 92 | node.nextSibling)); 93 | node.nodeValue = val.substr(0, pos); 94 | if (isInSVG) { 95 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 96 | var bbox = node.parentElement.getBBox(); 97 | rect.x.baseVal.value = bbox.x; 98 | rect.y.baseVal.value = bbox.y; 99 | rect.width.baseVal.value = bbox.width; 100 | rect.height.baseVal.value = bbox.height; 101 | rect.setAttribute('class', className); 102 | addItems.push({ 103 | "parent": node.parentNode, 104 | "target": rect}); 105 | } 106 | } 107 | } 108 | else if (!jQuery(node).is("button, select, textarea")) { 109 | jQuery.each(node.childNodes, function() { 110 | highlight(this, addItems); 111 | }); 112 | } 113 | } 114 | var addItems = []; 115 | var result = this.each(function() { 116 | highlight(this, addItems); 117 | }); 118 | for (var i = 0; i < addItems.length; ++i) { 119 | jQuery(addItems[i].parent).before(addItems[i].target); 120 | } 121 | return result; 122 | }; 123 | 124 | /* 125 | * backward compatibility for jQuery.browser 126 | * This will be supported until firefox bug is fixed. 127 | */ 128 | if (!jQuery.browser) { 129 | jQuery.uaMatch = function(ua) { 130 | ua = ua.toLowerCase(); 131 | 132 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 133 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 134 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 135 | /(msie) ([\w.]+)/.exec(ua) || 136 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 137 | []; 138 | 139 | return { 140 | browser: match[ 1 ] || "", 141 | version: match[ 2 ] || "0" 142 | }; 143 | }; 144 | jQuery.browser = {}; 145 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 146 | } 147 | 148 | /** 149 | * Small JavaScript module for the documentation. 150 | */ 151 | var Documentation = { 152 | 153 | init : function() { 154 | this.fixFirefoxAnchorBug(); 155 | this.highlightSearchWords(); 156 | this.initIndexTable(); 157 | if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { 158 | this.initOnKeyListeners(); 159 | } 160 | }, 161 | 162 | /** 163 | * i18n support 164 | */ 165 | TRANSLATIONS : {}, 166 | PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, 167 | LOCALE : 'unknown', 168 | 169 | // gettext and ngettext don't access this so that the functions 170 | // can safely bound to a different name (_ = Documentation.gettext) 171 | gettext : function(string) { 172 | var translated = Documentation.TRANSLATIONS[string]; 173 | if (typeof translated === 'undefined') 174 | return string; 175 | return (typeof translated === 'string') ? translated : translated[0]; 176 | }, 177 | 178 | ngettext : function(singular, plural, n) { 179 | var translated = Documentation.TRANSLATIONS[singular]; 180 | if (typeof translated === 'undefined') 181 | return (n == 1) ? singular : plural; 182 | return translated[Documentation.PLURALEXPR(n)]; 183 | }, 184 | 185 | addTranslations : function(catalog) { 186 | for (var key in catalog.messages) 187 | this.TRANSLATIONS[key] = catalog.messages[key]; 188 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 189 | this.LOCALE = catalog.locale; 190 | }, 191 | 192 | /** 193 | * add context elements like header anchor links 194 | */ 195 | addContextElements : function() { 196 | $('div[id] > :header:first').each(function() { 197 | $('\u00B6'). 198 | attr('href', '#' + this.id). 199 | attr('title', _('Permalink to this headline')). 200 | appendTo(this); 201 | }); 202 | $('dt[id]').each(function() { 203 | $('\u00B6'). 204 | attr('href', '#' + this.id). 205 | attr('title', _('Permalink to this definition')). 206 | appendTo(this); 207 | }); 208 | }, 209 | 210 | /** 211 | * workaround a firefox stupidity 212 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 213 | */ 214 | fixFirefoxAnchorBug : function() { 215 | if (document.location.hash && $.browser.mozilla) 216 | window.setTimeout(function() { 217 | document.location.href += ''; 218 | }, 10); 219 | }, 220 | 221 | /** 222 | * highlight the search words provided in the url in the text 223 | */ 224 | highlightSearchWords : function() { 225 | var params = $.getQueryParameters(); 226 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 227 | if (terms.length) { 228 | var body = $('div.body'); 229 | if (!body.length) { 230 | body = $('body'); 231 | } 232 | window.setTimeout(function() { 233 | $.each(terms, function() { 234 | body.highlightText(this.toLowerCase(), 'highlighted'); 235 | }); 236 | }, 10); 237 | $('') 239 | .appendTo($('#searchbox')); 240 | } 241 | }, 242 | 243 | /** 244 | * init the domain index toggle buttons 245 | */ 246 | initIndexTable : function() { 247 | var togglers = $('img.toggler').click(function() { 248 | var src = $(this).attr('src'); 249 | var idnum = $(this).attr('id').substr(7); 250 | $('tr.cg-' + idnum).toggle(); 251 | if (src.substr(-9) === 'minus.png') 252 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 253 | else 254 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 255 | }).css('display', ''); 256 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 257 | togglers.click(); 258 | } 259 | }, 260 | 261 | /** 262 | * helper function to hide the search marks again 263 | */ 264 | hideSearchWords : function() { 265 | $('#searchbox .highlight-link').fadeOut(300); 266 | $('span.highlighted').removeClass('highlighted'); 267 | }, 268 | 269 | /** 270 | * make the url absolute 271 | */ 272 | makeURL : function(relativeURL) { 273 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 274 | }, 275 | 276 | /** 277 | * get the current relative url 278 | */ 279 | getCurrentURL : function() { 280 | var path = document.location.pathname; 281 | var parts = path.split(/\//); 282 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 283 | if (this === '..') 284 | parts.pop(); 285 | }); 286 | var url = parts.join('/'); 287 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 288 | }, 289 | 290 | initOnKeyListeners: function() { 291 | $(document).keydown(function(event) { 292 | var activeElementType = document.activeElement.tagName; 293 | // don't navigate when in search box, textarea, dropdown or button 294 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' 295 | && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey 296 | && !event.shiftKey) { 297 | switch (event.keyCode) { 298 | case 37: // left 299 | var prevHref = $('link[rel="prev"]').prop('href'); 300 | if (prevHref) { 301 | window.location.href = prevHref; 302 | return false; 303 | } 304 | break; 305 | case 39: // right 306 | var nextHref = $('link[rel="next"]').prop('href'); 307 | if (nextHref) { 308 | window.location.href = nextHref; 309 | return false; 310 | } 311 | break; 312 | } 313 | } 314 | }); 315 | } 316 | }; 317 | 318 | // quick alias for translations 319 | _ = Documentation.gettext; 320 | 321 | $(document).ready(function() { 322 | Documentation.init(); 323 | }); 324 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | devicely, a python package for reading, timeshifting and writing sensor data — devicely 1.1.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 108 | 109 |
110 | 111 | 112 | 118 | 119 | 120 |
121 | 122 |
123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
143 | 144 |
    145 | 146 |
  • »
  • 147 | 148 |
  • devicely, a python package for reading, timeshifting and writing sensor data
  • 149 | 150 | 151 |
  • 152 | 153 | 154 | View page source 155 | 156 | 157 |
  • 158 | 159 |
160 | 161 | 162 |
163 |
164 |
165 |
166 | 167 | 168 | 191 |
192 |

devicely, a python package for reading, timeshifting and writing sensor data

193 |

The devicely package is made for reading, writing and de-identifying health sensor data from several sensors:

194 |
    195 |
  • Empatica E4 is a wearable device that offers real-time physiological data acquisition such as blood volume pulse, electrodermal activity (EDA), heart rate, interbeat intervals, 3-axis acceleration and skin temperature.

  • 196 |
  • Biovotion Everion is a wearable device used for the continuous monitoring of vital signs. Currently, it measures the following vital signs: heart rate, blood pulse wave, heart rate variability, activity, SPO2, blood perfusion, respiration rate, steps, energy expenditure, skin temperature, EDA / galvanic skin response (GSR), barometric pressure and sleep.

  • 197 |
  • 1-lead ECG monitor Faros 180 from Bittium is a one channel ECG monitor with sampling frequency up to 1000 Hz and a 3D acceleration sampling up to 100Hz.

  • 198 |
  • Spacelabs (SL 90217) is an oscillometric blood pressure (BP) monitor which can be used to automatically track a person’s BP in specificed time intervals.

  • 199 |
  • TimeStamp for Android allows you to record the timestamp of an event at the time it occurs. It also allows you to create specific tags such as “Running” or “Walking” and timestamp those specific activities.

  • 200 |
  • Shimmer Consensys GSR is a device that is used to collect sensor data in real time and it contains sensors such as GSR / EDA, photoplethysmography (PPG), 3-axis accelerometer, 3-axis gyroscope, 3-axis magnetometer & integrated altimeter.

  • 201 |
  • Muse S is a consumer grade headband consisting of 4 electrodes electroencephalography (EEG) sensors, 3-axis accelerometer (ACC), gyroscope, and photoplethysmography (PPG) sensors.

  • 202 |
203 |

devicely makes it easy to read sensor data and access it in common formats such as dataframes.

204 |

We provide a custom reader class for each sensor which has three core methods: read, timeshift and write. 205 | The timeshift method changes the time of measurement, thereby automatically 206 | helping to de-identify the data.

207 |

After timeshifting, you can write the data in the original data format. 208 | Now you have one more safety measure in place that will facilitate data sharing.

209 | 217 |
218 |

About devicely

219 | 224 |
225 |
226 |
227 |
228 | 229 | 230 |
231 | 232 |
233 |
234 | 237 | 238 |
239 | 240 |
241 |

242 | © Copyright 2021, Digital Health Center (Hasso Plattner Institute). 243 | 244 |

245 |
246 | 247 | 248 | 249 | Built with Sphinx using a 250 | 251 | theme 252 | 253 | provided by Read the Docs. 254 | 255 |
256 |
257 |
258 | 259 |
260 | 261 |
262 | 263 | 264 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /devicely/faros.py: -------------------------------------------------------------------------------- 1 | """ 2 | 1-lead ECG monitor FarosTM 180 from Bittium is a one channel ECG monitor with 3 | sampling frequency up to 1000 Hz and a 3D acceleration sampling up to 100Hz. 4 | """ 5 | 6 | import json 7 | import os 8 | import random 9 | 10 | import numpy as np 11 | import pandas as pd 12 | import pyedflib as edf 13 | 14 | 15 | class FarosReader: 16 | """ 17 | Read, timeshift and write data 18 | generated by Bittium Faros devices. 19 | 20 | Attributes 21 | ---------- 22 | start_time : pandas.Timestamp 23 | Start time of all measurements. 24 | 25 | sample_freqs : dict 26 | Sampling frequencies of all signals in Hz. 27 | 28 | units : dict 29 | Units of all signals 30 | 31 | ECG : pandas.Series 32 | ECG signal, indexed by timestamp. 33 | 34 | ACC : pandas.DataFrame 35 | Three ACC axes, indexed by timestamp. 36 | 37 | Marker : pandas.Series 38 | Markers, indexed by timestamp. 39 | 40 | HRV : pandas.Series 41 | HRV signal, indexed by timestamp. 42 | 43 | data : DataFrame 44 | Contain all signals (ECG, ACC, Marker, HRV) indexed by timestamp. 45 | Since the signals have different sampling frequencies, many values will be NaN. 46 | """ 47 | 48 | def __init__(self, path): 49 | """ 50 | Read a Faros-generated EDF file or a directory created by a FarosReader. 51 | 52 | Parameters 53 | ---------- 54 | path : str 55 | Can either be a Faros-generated EDF file or a directory created by the FarosReader.write() method. 56 | """ 57 | 58 | self.start_time = None 59 | self.sample_freqs = None 60 | self.units = None 61 | self._edf_metadata = None 62 | self.ECG = None 63 | self.ACC = None 64 | self.Marker = None 65 | self.HRV = None 66 | self.data = None 67 | 68 | if os.path.isfile(path): 69 | self._read_from_edf_file(path) 70 | if os.path.isdir(path): 71 | self._read_from_directory(path) 72 | 73 | def _read_from_edf_file(self, path): 74 | reader = edf.EdfReader(path) 75 | self.start_time = pd.Timestamp(reader.getStartdatetime()) 76 | self.sample_freqs = { 77 | 'ECG': reader.getSampleFrequency(0), 78 | 'ACC': reader.getSampleFrequency(1), 79 | 'Marker': reader.getSampleFrequency(4), 80 | 'HRV': reader.getSampleFrequency(5) 81 | } 82 | self.units = { 83 | 'ECG': reader.getSignalHeader(0)['dimension'], 84 | 'ACC': reader.getSignalHeader(1)['dimension'], 85 | 'HRV': reader.getSignalHeader(5)['dimension'], 86 | } 87 | self._edf_metadata = reader.getSignalHeaders() 88 | 89 | self._n_samples = reader.getNSamples() 90 | self._n_datarecords = reader.datarecords_in_file 91 | 92 | ecg = reader.readSignal(0) 93 | self.ECG = pd.Series(ecg, name='ECG', 94 | index=pd.date_range(start=self.start_time, 95 | periods=len(ecg), 96 | freq=f"{1/self.sample_freqs['ECG']}S") 97 | ) 98 | 99 | acc = np.array([reader.readSignal(i) for i in range(1, 4)]).T 100 | self.ACC = pd.DataFrame(acc, columns=['X', 'Y', 'Z'], 101 | index=pd.date_range(start=self.start_time, 102 | periods=len(acc), 103 | freq=f"{1/self.sample_freqs['ACC']}S") 104 | ) 105 | 106 | marker = reader.readSignal(4) 107 | self.Marker = pd.Series(marker, name='Marker', 108 | index=pd.date_range(start=self.start_time, 109 | periods=len(marker), 110 | freq=f"{1/self.sample_freqs['Marker']}S") 111 | ) 112 | 113 | hrv = reader.readSignal(5) 114 | self.HRV = pd.Series( 115 | hrv, name='HRV', 116 | index=pd.date_range(start=self.start_time, 117 | periods=len(hrv), 118 | freq=f"{1/self.sample_freqs['HRV']}S") 119 | ) 120 | 121 | reader.close() 122 | 123 | def _read_from_directory(self, path): 124 | with open(os.path.join(path, 'meta.json'), 'r') as meta_file: 125 | meta = json.load(meta_file) 126 | self.start_time = pd.Timestamp(meta['start_time']) 127 | self.sample_freqs = meta['sample_freqs'] 128 | self.units = meta['units'] 129 | 130 | self.ECG = self._read_dir_csv(os.path.join(path, 'ECG.csv'), self.start_time, self.sample_freqs['ECG']) 131 | self.ACC = self._read_dir_csv(os.path.join(path, 'ACC.csv'), self.start_time, self.sample_freqs['ACC']) 132 | self.Marker = self._read_dir_csv(os.path.join(path, 'Marker.csv'), self.start_time, self.sample_freqs['Marker']) 133 | self.HRV = self._read_dir_csv(os.path.join(path, 'HRV.csv'), self.start_time, self.sample_freqs['HRV']) 134 | 135 | def _read_dir_csv(self, path, start_time, sample_freq): 136 | dataframe = pd.read_csv(path) 137 | idx = pd.date_range(start=start_time, periods=len(dataframe), freq=f"{1/sample_freq}S") 138 | dataframe.index = idx 139 | 140 | return dataframe.squeeze() 141 | 142 | def join_dataframes(self): 143 | """ 144 | Join the individual signal dataframes by timestamp. 145 | The resulting dataframe is saved in the attribute reader.data. 146 | """ 147 | # get index for joined dataframe 148 | joined_idx = pd.concat(map(pd.Series, [self.ECG.index, self.ACC.index, self.Marker.index, self.HRV.index])) 149 | joined_idx = pd.Index(joined_idx.drop_duplicates().sort_values()) 150 | 151 | # create joined dataframe 152 | col_names = ['ECG', 'ACC_X', 'ACC_Y', 'ACC_Z', 'ACC_mag', 'Marker', 'HRV'] 153 | joined_df = pd.DataFrame(index=joined_idx, columns=col_names) 154 | 155 | # set non-nan values of joined dataframe 156 | joined_df.loc[self.ECG.index, 'ECG'] = self.ECG 157 | joined_df.loc[self.ACC.index, 'ACC_X'] = self.ACC['X'] 158 | joined_df.loc[self.ACC.index, 'ACC_Y'] = self.ACC['Y'] 159 | joined_df.loc[self.ACC.index, 'ACC_Z'] = self.ACC['Z'] 160 | joined_df.loc[self.Marker.index, 'Marker'] = self.Marker 161 | joined_df.loc[self.HRV.index, 'HRV'] = self.HRV 162 | 163 | self.data = joined_df 164 | 165 | def write(self, path, file_format='directory'): 166 | """ 167 | Write the data either to an EDF file or to several files into a new directory. 168 | Because of the `special structure of EDF files `_ 169 | writing to EDF is only possible for readers that have been created from an EDF file and without any changes to the ACC, ECG, Marker, HRV and sample_freqs attributes. 170 | Because we want you to be able to modify the signals, you can write the data back to a directory of individual files. 171 | Writing to a directory is the preferred method and works in all cases. 172 | 173 | Parameters 174 | ---------- 175 | path : str 176 | Name of the file or directory to write the data to. 177 | file_format: {'directory', 'edf'}, default 'directory' 178 | Format of the written data. 179 | """ 180 | 181 | if file_format == 'directory': 182 | self._write_to_directory(path) 183 | if file_format == 'edf': 184 | self._write_to_edf(path) 185 | 186 | def _write_to_edf(self, path): 187 | if self._edf_metadata is None: 188 | raise Exception("There is no EDF metadata in this reader, most likely because it was initialized from a directory. Writing to EDF file not possible.") 189 | 190 | writer = edf.EdfWriter(path, 6, 0) 191 | writer.setStartdatetime(self.start_time.to_pydatetime()) 192 | writer.setSignalHeaders(self._edf_metadata) 193 | 194 | ecg_freq = int(self.sample_freqs['ECG']) 195 | acc_freq = int(self.sample_freqs['ACC']) 196 | marker_freq = int(self.sample_freqs['Marker']) 197 | hrv_freq = int(self.sample_freqs['HRV']) 198 | 199 | n_records = int(len(self.ECG) / ecg_freq) 200 | 201 | for i in range(n_records): 202 | writer.writePhysicalSamples(self.ECG.values[ecg_freq * i: ecg_freq * (i + 1)]) 203 | writer.writePhysicalSamples(self.ACC['X'].values[acc_freq * i: acc_freq * (i + 1)]) 204 | writer.writePhysicalSamples(self.ACC['Y'].values[acc_freq * i: acc_freq * (i + 1)]) 205 | writer.writePhysicalSamples(self.ACC['Z'].values[acc_freq * i: acc_freq * (i + 1)]) 206 | writer.writePhysicalSamples(self.Marker.values[marker_freq * i: marker_freq * (i + 1)]) 207 | writer.writePhysicalSamples(self.HRV.values[hrv_freq * i: hrv_freq * (i + 1)]) 208 | 209 | writer.close() 210 | 211 | def _write_to_directory(self, path): 212 | if not os.path.isdir(path): 213 | os.mkdir(path) 214 | 215 | meta = { 216 | 'start_time': str(self.start_time), 217 | 'sample_freqs': self.sample_freqs, 218 | 'units': self.units 219 | } 220 | with open(os.path.join(path, 'meta.json'), 'w') as meta_file: 221 | json.dump(meta, meta_file) 222 | 223 | self.ECG.to_csv(os.path.join(path, 'ECG.csv'), index=None, line_terminator='\n') 224 | self.ACC.to_csv(os.path.join(path, 'ACC.csv'), index=None, line_terminator='\n') 225 | self.Marker.to_csv(os.path.join(path, 'Marker.csv'), index=None, line_terminator='\n') 226 | self.HRV.to_csv(os.path.join(path, 'HRV.csv'), index=None, line_terminator='\n') 227 | 228 | def timeshift(self, shift='random'): 229 | """ 230 | Timeshift the data by shifting all time related values (i.e. start_time 231 | and data.index). 232 | 233 | Parameters 234 | ---------- 235 | shift : None/'random', pd.Timestamp or pd.Timedelta 236 | If shift is not specified, shifts the data by a random time interval 237 | between one month and two years to the past. 238 | 239 | If shift is a timdelta, shifts the data by that timedelta. 240 | 241 | If shift is a timestamp, shifts the data such that the earliest entry 242 | has that timestamp. The remaining values will mantain the same 243 | time difference to the first entry. 244 | """ 245 | if shift == 'random': 246 | one_month = pd.Timedelta('30 days').value 247 | two_years = pd.Timedelta('730 days').value 248 | random_timedelta = - pd.Timedelta(random.uniform(one_month, two_years)).round('s') 249 | self.timeshift(random_timedelta) 250 | 251 | dfs_to_shift = [self.ECG, self.ACC, self.Marker, self.HRV] 252 | if self.data is not None: 253 | dfs_to_shift.append(self.data) 254 | 255 | if isinstance(shift, pd.Timestamp): 256 | self.start_time = shift 257 | for dataframe in dfs_to_shift: 258 | timedeltas = dataframe.index - dataframe.index.min() 259 | dataframe.index = shift + timedeltas 260 | if isinstance(shift, pd.Timedelta): 261 | for dataframe in dfs_to_shift: 262 | dataframe.index += shift 263 | --------------------------------------------------------------------------------