diff --git a/.codacy.yml b/.codacy.yml deleted file mode 100644 index e93c0c2..0000000 --- a/.codacy.yml +++ /dev/null @@ -1,5 +0,0 @@ -# Config file for automatic testing at codacy.com ---- -exclude_paths: - - 'tests/*' - - 'setup.py' diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 1ea0ba4..0000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[report] -omit = - setup.py - tests/* \ No newline at end of file diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml new file mode 100644 index 0000000..9017fa9 --- /dev/null +++ b/.github/workflows/conda-package.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + schedule: + # every sunday at 14:00 UTC + - cron: '0 14 * * SUN' + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8] + name: ${{ matrix.os }} - Python ${{ matrix.python-version }} + steps: + - uses: actions/checkout@master + + - name: Setup conda + uses: s-weigand/setup-conda@v1 + with: + activate-conda: true + update-conda: true + python-version: ${{ matrix.python-version }} + conda-channels: conda-forge + + - name: Add path DLL to windows path (windows only) + if: startsWith(matrix.os, 'windows') + run: echo "##[add-path]C:\Miniconda\Library\bin" + + - name: Install Dependencies + run: | + conda env update -f environment.yml -n base + pip install -r requirements-dev.txt + conda info -a + + - name: Lint (ubuntu only) + if: startsWith(matrix.os, 'ubuntu') + run: black --check . + + - name: Test + run: | + cp README.md docs/index.md + python -m pytest --cov-report term-missing --color=yes --cov=pyomeca tests + + - name: Coverage (ubuntu only) + if: startsWith(matrix.os, 'ubuntu') + env: + COVERALLS_REPO_TOKEN: PZM3oz4Z1NbKgI9rvS7Of89vA7KdHsDAE + run: | + pip install coveralls + coveralls diff --git a/.gitignore b/.gitignore index 14cf566..51e0335 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -8,7 +10,6 @@ __pycache__/ # Distribution / packaging .Python -env/ build/ develop-eggs/ dist/ @@ -20,9 +21,13 @@ lib64/ parts/ sdist/ var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg +MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -37,14 +42,15 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml -*,cover +*.cover .hypothesis/ -.pytest_cache +.pytest_cache/ # Translations *.mo @@ -52,6 +58,16 @@ coverage.xml # Django stuff: *.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy # Sphinx documentation docs/_build/ @@ -62,9 +78,30 @@ target/ # Jupyter Notebook .ipynb_checkpoints +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + # Environments .env .venv +env/ venv/ ENV/ env.bak/ @@ -82,11 +119,29 @@ venv.bak/ # mypy .mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +misc -# pycharm .idea -# Personnal stuff -Misc/ +site/ + +todo.txt + +temp.* + +docs/api/api.json +/docs/images/api/*.svg +/docs/getting-started.md +/docs/*_files +/docs/index.md +emg.* +trials.nc +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e893a91..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Config file for automatic testing at travis-ci.org -language: python - -before_install: - - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - - bash miniconda.sh -b -p $HOME/miniconda - - export PATH="$HOME/miniconda/bin:$PATH" - - hash -r - - conda config --set always_yes yes --set changeps1 no - - conda config --set auto_update_conda no - - conda update -q conda - -install: - - conda env update -n root -f environment.yml - -script: - - black --check pyomeca/ - - pytest -v --color=yes --cov=pyomeca tests - -after_success: - - conda install -c conda-forge codecov - - codecov - -notifications: - email: - on_success: never - on_failure: always diff --git a/LICENSE b/LICENSE.md similarity index 99% rename from LICENSE rename to LICENSE.md index 261eeb9..8ccc35e 100644 --- a/LICENSE +++ b/LICENSE.md @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright Romain Martinez & Benjamin Michaud, 2020 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile index 83ec764..1176f26 100644 --- a/Makefile +++ b/Makefile @@ -1,112 +1,26 @@ -.PHONY: test cover clean lint create_env +.PHONY : test lint doc nb_to_md clean all -################################################################################# -# GLOBALS # -################################################################################# - -REPO_NAME = pyomeca -EXCLUDES_LINT = --exclude=bin/,src/rebuydsutils/,docs/conf.py -EXCLUDES_PYTEST = --ignore src/rebuydsutils -SHELL=/bin/bash - -ifeq (,$(shell which conda)) - $(error conda must be installed) -endif - -# Define utility variable to help calling Python from the virtual environment -ifeq ($(CONDA_DEFAULT_ENV),$(REPO_NAME)) - ACTIVATE_ENV := true -else - ACTIVATE_ENV := source activate $(REPO_NAME) -endif - -# Execute python related functionalities from within the project's environment -define execute_in_env - $(ACTIVATE_ENV) && $1 -endef - -################################################################################# -# PROJECT RULES # -################################################################################# +lint: + isort -rc . ;\ + autoflake -r --in-place --remove-unused-variables . ;\ + black . -## Run pytest on the project test: - $(call execute_in_env, python -m pytest --color=yes tests $(EXCLUDES_PYTEST)) + pytest --cov-report term-missing --color=yes --cov=pyomeca tests -rxXs -## Run coverage test on the project -cover: - $(call execute_in_env, python -m pytest --color=yes --cov=pyomeca tests $(EXCLUDES_PYTEST)) +nb_to_md: + jupyter nbconvert --to markdown notebooks/getting-started.ipynb --output-dir='./docs' --template=docs/nbconvert.tpl -## Delete all compiled Python files -clean: - find . -name "*.pyc" -exec rm {} \; - -## Lint using flake8; Excluding $EXCLUDES_LINT -lint: - $(call execute_in_env, flake8 $(EXCLUDES_LINT) .) +doc: + # copy readme, correct path and delete link to documentation + sed 's,docs/,,g' README.md > docs/index.md; \ + sed -i -z "s,\n## Pyomeca documentation\n\nSee Pyomeca's \[documentation site\](https://pyomeca.github.io).\n,,g" docs/index.md; \ + sed -i -z "s,\nSee \[the documentation\](https://pyomeca.github.io) for more details and examples.\n,,g" docs/index.md; \ + cd ../pyomeca.github.io; \ + mkdocs gh-deploy --config-file ../pyomeca/mkdocs.yml --remote-branch master + rm -rf site -## Set up python interpreter environment -create_env: - conda env create -n $(REPO_NAME) -f environment.yml - rm -rf *.egg-info - -################################################################################# -# Self Documenting Commands # -################################################################################# - -.DEFAULT_GOAL := help +clean: + rm -rf .pytest_cache .coverage site notebooks/.ipynb_checkpoints -# Inspired by -# sed script explained: -# /^##/: -# * save line in hold space -# * purge line -# * Loop: -# * append newline + line to hold space -# * go to next line -# * if line starts with doc comment, strip comment character off and loop -# * remove target prerequisites -# * append hold space (+ newline) to line -# * replace newline plus comments by `---` -# * print line -# Separate expressions are necessary because labels cannot be delimited by -# semicolon; see -.PHONY: help -help: - @echo "$$(tput bold)Available rules:$$(tput sgr0)" - @echo - @sed -n -e "/^## / { \ - h; \ - s/.*//; \ - :doc" \ - -e "H; \ - n; \ - s/^## //; \ - t doc" \ - -e "s/:.*//; \ - G; \ - s/\\n## /---/; \ - s/\\n/ /g; \ - p; \ - }" ${MAKEFILE_LIST} \ - | LC_ALL='C' sort --ignore-case \ - | awk -F '---' \ - -v ncol=$$(tput cols) \ - -v indent=19 \ - -v col_on="$$(tput setaf 6)" \ - -v col_off="$$(tput sgr0)" \ - '{ \ - printf "%s%*s%s ", col_on, -indent, $$1, col_off; \ - n = split($$2, words, " "); \ - line_length = ncol - indent; \ - for (i = 1; i <= n; i++) { \ - line_length -= length(words[i]) + 1; \ - if (line_length <= 0) { \ - line_length = ncol - indent - length(words[i]) - 1; \ - printf "\n%*s ", -indent, " "; \ - } \ - printf "%s ", words[i]; \ - } \ - printf "\n"; \ - }' \ - | more $(shell test $(shell uname) = Darwin && echo '--no-init --raw-control-chars') +all: lint nb_to_md test doc clean diff --git a/README.md b/README.md index cc37851..fa89b13 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,131 @@ - +

+ logo + Actions Status + Coverage Status + License: MIT + PyPI + Downloads + Code style: black +

+ +Pyomeca is a python library allowing you to carry out a complete biomechanical analysis; in a simple, logical and concise way. -# Pyomeca - -> Pyomeca is a python library allowing you to carry out a complete biomechanical analysis; in a simple, logical and concise way. +## Pyomeca documentation -## Status +See Pyomeca's [documentation site](https://pyomeca.github.io). -| | | -|---|---| -| Continuous integration (Linux) | [![Build Status](https://travis-ci.org/pyomeca/pyomeca.svg?branch=travis)](https://travis-ci.org/pyomeca/pyomeca) | -| Continuous integration (Windows) | [![Build status](https://ci.appveyor.com/api/projects/status/c988kaow6dbac3lk?svg=true)](https://ci.appveyor.com/project/romainmartinez/pyomeca) | -| Code coverage | [![codecov](https://codecov.io/gh/pyomeca/pyomeca/branch/dev/graph/badge.svg)](https://codecov.io/gh/pyomeca/pyomeca) | -| Code quality | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/89e663b2541b4575bcccc37b63dfb462)](https://www.codacy.com/app/romainmartinez/pyomeca?utm_source=github.com&utm_medium=referral&utm_content=pyomeca/pyomeca&utm_campaign=Badge_Grade) | -| Last release | [![Anaconda-Server Badge](https://anaconda.org/conda-forge/pyomeca/badges/latest_release_relative_date.svg)](https://anaconda.org/conda-forge/pyomeca) | -| Installation | [![Anaconda-Server Badge](https://anaconda.org/conda-forge/pyomeca/badges/installer/conda.svg)](https://anaconda.org/conda-forge/pyomeca) | +## Example -## Pyomeca documentation +Pyomeca implements specialized functionalities commonly used in biomechanics. +As an example, let's process the electromyographic data contained in this [`c3d file`](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers_analogs.c3d). -See pyomeca's [documentation site]() (under construction and not usable yet), as well as pyomeca's [tutorial notebooks](https://github.com/pyomeca/tutorials). +You can follow along without installing anything by using our binder server: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyomeca/pyomeca/master?filepath=notebooks) -## Example +```python +from pyomeca import Analogs + +data_path = "../tests/data/markers_analogs.c3d" +muscles = [ + "Delt_ant", + "Delt_med", + "Delt_post", + "Supra", + "Infra", + "Subscap", +] +emg = Analogs.from_c3d(data_path, suffix_delimiter=".", usecols=muscles) +emg.plot(x="time", col="channel", col_wrap=3) +``` -Here is an example of a complete EMG pipeline in just one command: +![svg](docs/images/readme-example_files/readme-example_3_0.svg) ```python -from pyomeca import Analogs3d - -emg = ( - Analogs3d.from_c3d("path/to/your/c3d.c3d", names=['anterior_deltoid', 'biceps']) - .band_pass(freq=2000, order=4, cutoff=[10, 425]) - .center() - .rectify() - .low_pass(freq=2000, order=4, cutoff=5) - .normalization() - .time_normalization() +emg_processed = ( + emg.meca.band_pass(order=2, cutoff=[10, 425]) + .meca.center() + .meca.abs() + .meca.low_pass(order=4, cutoff=5, freq=emg.rate) + .meca.normalize() ) -``` -## Features +emg_processed.plot(x="time", col="channel", col_wrap=3) +``` -- Object-oriented architecture where each class is associated with common and specialized functionalities: - - **Markers3d**: 3d markers positions - - **Analogs3d**: analogs (emg, force or any analog signal) - - **GeneralizedCoordinate**: generalized coordinate (joint angle) - - **RotoTrans**: roto-translation matrix +![svg](docs/images/readme-example_files/readme-example_4_0.svg) +```python +import matplotlib.pyplot as plt -- Specialized functionalities include signal processing routine commonly used in biomechanics: filters, normalization, onset detection, outliers detection, derivative, etc. +fig, axes = plt.subplots(ncols=2, figsize=(10, 4)) +emg_processed.mean("channel").plot(ax=axes[0]) +axes[0].set_title("Mean EMG activation") -- Each functionality can be chained. In addition to making it easier to write and read code, it allows you to add and remove analysis steps easily (such as Lego blocks). +emg_processed.plot.hist(ax=axes[1], bins=50) +axes[1].set_title("EMG activation distribution") +``` +![svg](docs/images/readme-example_files/readme-example_5_1.svg) -- Each class inherits from a numpy array, so you can create your own analysis step easily. +See [the documentation](https://pyomeca.github.io) for more details and examples. +## Features -- Easy reading and writing interface to common files in biomechanics: - - **c3d** (binary file used in biomechanics): `from_c3d` and `to_c3d` - - **csv**: `from_csv` and `to_csv` - - **mat** (_MATLAB_ file): `from_mat` and `to_mat` - - **sto** (OpenSim storage file): `to_sto` (must install pyosim) - - **trc** (OpenSim markers position file): `to_trc` (must install pyosim) +- Signal processing routine commonly used in biomechanics such as filters, normalization, onset detection, outliers detection, derivatives, etc. +- Common matrix manipulation routines implemented such as getting Euler angles to/from a rototranslation matrix, creating a system of axes, setting a rotation or translation, transpose or inverse, etc. +- Easy reading and writing interface to common files in biomechanics (`c3d`, `csv`, `xlsx`,`mat`, `trc`, `sto`, `mot`) +- All of [xarray](http://xarray.pydata.org/en/stable/index.html)'s awesome features +The following illustration shows all of pyomeca's public API. +An interactive version is available in the [documentation](https://pyomeca.github.io/overview/). -- Common linear algebra routine implemented: get Euler angles to/from roto-translation matrix, create a system of axes, set a rotation or translation, transpose or inverse, etc. +

+ api +

## Installation -### Using Conda +Pyomeca itself is a pure Python package, but its dependencies are not. +The easiest way to get everything installed is to use [conda](https://conda.io/en/latest/miniconda.html). -First, install [miniconda](https://conda.io/miniconda.html) or [anaconda](https://www.anaconda.com/download/). -Then type: +To install pyomeca with its recommended dependencies using the conda command line tool: -``` -conda install pyomeca -c conda-forge +```bash +conda install -c conda-forge pyomeca ``` -### Using pip +Now that you have installed pyomeca, you should be able to import it: -First, you need to install python, swig and numpy. -Then, follow the instructions to compile [ezc3d](https://github.com/pyomeca/ezc3d). -Finally, install pyomeca with `pip install git+https://github.com/pyomeca/pyomeca/`. +```python +import pyomeca +``` ## Integration with other modules @@ -89,12 +134,19 @@ Pyomeca is designed to work well with other libraries that we have developed: - [pyosim](https://github.com/pyomeca/pyosim): interface between [OpenSim](http://opensim.stanford.edu/) and pyomeca to perform batch musculoskeletal analyses - [ezc3d](https://github.com/pyomeca/ezc3d): Easy to use C3D reader/writer in C++, Python and Matlab - [biorbd](https://github.com/pyomeca/biorbd): C++ interface and add-ons to the Rigid Body Dynamics Library, with Python and Matlab binders. -- [pyoviz](https://github.com/pyomeca/pyoviz): pyomeca visualization toolkit and GUIs (still in development and not usable yet) -## Bug Reports & Questions +## Bug reports & questions Pyomeca is Apache-licensed and the source code is available on [GitHub](https://github.com/pyomeca/pyomeca). - If any questions or issues come up as you use pyomeca, please get in touch via [GitHub issues](https://github.com/pyomeca/pyomeca/issues). - We welcome any input, feedback, bug reports, and contributions. + +## Contributors and support + +- [Romain Martinez](https://github.com/romainmartinez) +- [Benjamin Michaud](https://github.com/pariterre) +- [Mickael Begon](https://github.com/mickaelbegon) +- [Jenn Dowling-Medley](https://github.com/jdowlingmedley) +- [Ariane Dang](https://github.com/Dangzilla) + +Pyomeca is an open-source project created and supported by the [S2M lab](https://www.facebook.com/s2mlab/). diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 58f3b71..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Config file for automatic testing at appveyor.com -environment: - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script intepreter - # See: http://stackoverflow.com/a/13751649/163740 - CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\tools\\appveyor\\run_with_env.cmd" - - matrix: - - PYTHON: "C:\\Miniconda36-x64" - PYTHON_VERSION: "3.6" - PYTHON_ARCH: "64" - -init: - - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% %HOME% - - -install: - # If there is a newer build queued for the same PR, cancel this one. - # The AppVeyor 'rollout builds' option is supposed to serve the same - # purpose but it is problematic because it tends to cancel builds pushed - # directly to master instead of just PR builds (or the converse). - # credits: JuliaLang developers. - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } - # these correspond to folder naming of miniconda installs on appveyor. See - # https://www.appveyor.com/docs/installed-software#python - - if "%PYTHON_VERSION%" == "3.6" set "BASE_PYTHON_VERSION=36" - - if "%PYTHON_ARCH%" == "64" set "ARCH_LABEL=-x64" - - call "C:\Miniconda%BASE_PYTHON_VERSION%%ARCH_LABEL%\Scripts\activate.bat" - - conda config --set always_yes yes - - conda update -q conda - - conda config --set auto_update_conda no - - conda update -q --all - - conda info - # this is to ensure dependencies - - conda env update -n root -f environment.yml - - conda info -a - - -# Not a .NET project, we build package in the install step instead -build: false - -test_script: - - py.test --color=yes -v --cov pyomeca --cov-report xml tests - -on_success: - - conda install -c conda-forge codecov - - codecov --env PYTHON_VERSION --file C:\projects\pyomeca\coverage.xml diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 0000000..a869fd2 --- /dev/null +++ b/docs/about.md @@ -0,0 +1,49 @@ +## Licence + +Pyomeca is Apache-licensed and the source code is available on [GitHub](https://github.com/pyomeca/pyomeca). + +## Citing Pyomeca + +If you use pyomeca in your academic work, please consider citing [our paper]() as: + +```bibtex +@article{jos-pyomeca, + author = {author}, + title = {title}, + journal = {journal}, + year = {year}, +} +``` + +Please consider citing the [xarray](https://openresearchsoftware.metajnl.com/articles/10.5334/jors.148/) project, which pyomeca is based on: + +```bibtex +@article{jors-xarray, +title = {Xarray: N-D labeled arrays and datasets in Python}, +author = {Joe Hamman and Stephan Hoyer}, +year = {2017}, +journal = {Journal of Open Research Software} +} +``` + +## Papers citing Pyomeca + +- Blache, Yoann, Romain Martinez, Raphaël Dumas, Mickael Begon, Nicola Hagemeister, and Sonia Duprey. 2019. “[Chapter 20 - Motion Analysis and Modeling of the Shoulder: Challenges and Potential Applications.](https://www.sciencedirect.com/science/article/pii/B9780128167137000209)” In DHM and Posturography, edited by Sofia Scataglini and Gunther Paul, 261–271. Academic Press. doi:10.1016/B978-0-12-816713-7.00020-9. + +- Bouffard, Jason, Romain Martinez, André Plamondon, Julie N. Côté, and Mickaël Begon. 2019. “[Sex Differences in Glenohumeral Muscle Activation and Coactivation during a Box Lifting Task.](https://www.tandfonline.com/doi/abs/10.1080/00140139.2019.1640396)” Ergonomics 62 (10). Taylor & Francis: 1327–1338. doi:10.1080/00140139.2019.1640396. + +- Martinez, Romain, Najoua Assila, Etienne Goubault, and Mickaël Begon. 2020. “[Sex Differences in Upper Limb Musculoskeletal Biomechanics during a Lifting Task.](https://www.sciencedirect.com/science/article/abs/pii/S0003687020300673)” Applied Ergonomics 86 (July): 103106. doi:10.1016/j.apergo.2020.103106. + +- Martinez, Romain, Jason Bouffard, Benjamin Michaud, André Plamondon, Julie N. Côté, and Mickaël Begon. 2019. “[Sex Differences in Upper Limb 3D Joint Contributions during a Lifting Task.](https://www.tandfonline.com/doi/abs/10.1080/00140139.2019.1571245)” Ergonomics 62 (5): 682–693. doi:10.1080/00140139.2019.1571245. + +- Assila Najoua, Claudio Pizzolato, Romain Martinez, David Lloyd, Mickael Begon. 2020. “Toward a physiological model of the shoulder: EMG-assisted algorithm to account for the muscle co-contraction.” Annals of Biomedical Engineering. + +## Contributors and support + +- [Romain Martinez](https://github.com/romainmartinez) +- [Benjamin Michaud](https://github.com/pariterre) +- [Mickaël Begon](https://github.com/mickaelbegon) +- [Jenn Dowling-Medley](https://github.com/jdowlingmedley) +- [Ariane Dang](https://github.com/Dangzilla) + +Pyomeca is an open-source project created and supported by the [S2M lab](https://www.facebook.com/s2mlab/). diff --git a/docs/api/analogs.md b/docs/api/analogs.md new file mode 100644 index 0000000..f8b7619 --- /dev/null +++ b/docs/api/analogs.md @@ -0,0 +1,3 @@ +# [`analogs.py`](https://github.com/pyomeca/pyomeca/blob/master/pyomeca/analogs.py) + +::: pyomeca.analogs \ No newline at end of file diff --git a/docs/api/angles.md b/docs/api/angles.md new file mode 100644 index 0000000..a017a40 --- /dev/null +++ b/docs/api/angles.md @@ -0,0 +1,3 @@ +# [`angles.py`](https://github.com/pyomeca/pyomeca/blob/master/pyomeca/angles.py) + +::: pyomeca.angles \ No newline at end of file diff --git a/docs/api/dataarray_accessor.md b/docs/api/dataarray_accessor.md new file mode 100644 index 0000000..883a342 --- /dev/null +++ b/docs/api/dataarray_accessor.md @@ -0,0 +1,3 @@ +# [`dataarray_accessor.py`](https://github.com/pyomeca/pyomeca/blob/master/pyomeca/dataarray_accessor.py) + +::: pyomeca.dataarray_accessor \ No newline at end of file diff --git a/docs/api/markers.md b/docs/api/markers.md new file mode 100644 index 0000000..ab6ea74 --- /dev/null +++ b/docs/api/markers.md @@ -0,0 +1,3 @@ +# [`markers.py`](https://github.com/pyomeca/pyomeca/blob/master/pyomeca/markers.py) + +::: pyomeca.markers \ No newline at end of file diff --git a/docs/api/rototrans.md b/docs/api/rototrans.md new file mode 100644 index 0000000..c8a1168 --- /dev/null +++ b/docs/api/rototrans.md @@ -0,0 +1,3 @@ +# [`rototrans.py`](https://github.com/pyomeca/pyomeca/blob/master/pyomeca/rototrans.py) + +::: pyomeca.rototrans \ No newline at end of file diff --git a/docs/custom.css b/docs/custom.css new file mode 100644 index 0000000..cecb708 --- /dev/null +++ b/docs/custom.css @@ -0,0 +1,63 @@ +div.doc-contents { + border-bottom: 2px solid rgb(230, 230, 230); +} + +.center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.matrix-stroke { + stroke: #263238; + stroke-width: 0.5; +} + +.chart-text { + font-size: 16px; + fill: #263238 !important; +} + +.cell:hover rect { + fill-opacity: 0.7; +} + +.tooltip { + display: none; + position: fixed; + z-index: 1; + left: 50px; + top: 50px; + width: 40%; + background-color: #fefefe !important; +} + +.nb-output { + margin-left: 30px !important; + margin-right: 30px !important; + opacity: 0.8 !important; +} + +.nb-output button { + display: none; +} + +.xr-attrs dd { + margin: 0 !important; +} + +.xr-dim-list:before { + content: none !important; +} + +.xr-dim-list:after { + content: none !important; +} + +.xr-dim-list li:not(:last-child):after { + content: none !important; +} + +code th { + min-width: 0 !important; +} diff --git a/docs/data-processing.md b/docs/data-processing.md new file mode 100644 index 0000000..d330692 --- /dev/null +++ b/docs/data-processing.md @@ -0,0 +1,99 @@ +Pyomeca's main functionality is to offer dedicated biomechanical routines. + +

+ api +

+ +These features can be broadly grouped into different categories: filtering, normalization, matrix manipulation, signal processing and file output functions. + +## Filters + +Biomechanical data are inherently noisy. +And with noise, you will probably need filters. +Pyomeca implements the major types of Butterworth filters used in biomechanics. + +!!! example + === "Band pass" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.band_pass
+ + === "Band stop" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.band_stop
+ + === "High pass" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.high_pass
+ + === "Low pass" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.low_pass
+ +## Normalization + +It is common to use normalization procedures during biomechanical signal processing. +Pyomeca supports two types of normalization: signal normalization and time normalization. + +!!! example + === "Signal normalization" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.normalize
+ + === "Time normalization" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.time_normalize
+ +## Matrix manipulation + +The processing of biomechanical data often involves the use of matrix manipulation routines. +Some of them are implemented in Pyomeca. + +!!! example + === "Absolute value" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.abs
+ + === "Center signal" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.center
+ + === "Matmul" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.matmul
+ + === "Norm" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.norm
+ + === "RMS" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.rms
+ + === "Square" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.square
+ + === "Square root" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.sqrt
+ +## Signal processing + +Pyomeca implements convenient and flexible functions to detect onsets and outliers, as well as to compute a Fourier Transform. + +!!! example + === "Onsets detection" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.detect_onset
+ + === "Outliers detection" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.detect_outliers
+ + === "FFT" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.fft
+ +## File output + +While the [netcdf format](http://pyomeca.github.io/getting-started/#file-io) is the preferred file format for saving or sharing data structures, Pyomeca also supports writting csv and matlab files. +If you need more flexibility, the `to_wide_dataframe` method will allow you to use the pandas library to export your data in almost any existing formats. + +!!! example + === "Write csv file" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.to_csv
+ + === "Write matlab file" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.to_matlab
+ + === "Create wide pandas dataframe" +
/api/dataarray_accessor/#pyomeca.dataarray_accessor.DataArrayAccessor.to_wide_dataframe
+ + + diff --git a/docs/images/api.svg b/docs/images/api.svg new file mode 100644 index 0000000..589cab3 --- /dev/null +++ b/docs/images/api.svg @@ -0,0 +1 @@ +pyomecaAnglesRototransAnalogsMarkersDataArrayAccessorfrom_random_datafrom_rototransfrom_averaged_rototransfrom_euler_anglesfrom_markersfrom_random_datafrom_transposed_rototransfrom_c3dfrom_csvfrom_excelfrom_motfrom_random_datafrom_stofrom_c3dfrom_csvfrom_excelfrom_random_datafrom_rototransfrom_trcabsband_passband_stopcenterdetect_onsetdetect_outliersffthigh_passlow_passmatmulnormnormalizermssqrtsquaretime_normalizeto_csvto_matlabto_wide_dataframe \ No newline at end of file diff --git a/docs/images/api/.gitkeep b/docs/images/api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/images/data-processing.svg b/docs/images/data-processing.svg new file mode 100644 index 0000000..658eefd --- /dev/null +++ b/docs/images/data-processing.svg @@ -0,0 +1 @@ +DataArrayAccessorabsband_passband_stopcenterdetect_onsetdetect_outliersffthigh_passlow_passmatmulnormnormalizermssqrtsquaretime_normalizeto_csvto_matlabto_wide_dataframe \ No newline at end of file diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico new file mode 100644 index 0000000..fed9ee1 Binary files /dev/null and b/docs/images/favicon.ico differ diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100644 index 0000000..becf04b --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/object-creation.svg b/docs/images/object-creation.svg new file mode 100644 index 0000000..682c66b --- /dev/null +++ b/docs/images/object-creation.svg @@ -0,0 +1 @@ +AnglesRototransAnalogsMarkersfrom_random_datafrom_rototransfrom_averaged_rototransfrom_euler_anglesfrom_markersfrom_random_datafrom_transposed_rototransfrom_c3dfrom_csvfrom_excelfrom_motfrom_random_datafrom_stofrom_c3dfrom_csvfrom_excelfrom_random_datafrom_rototransfrom_trc \ No newline at end of file diff --git a/docs/images/objects/analogs.svg b/docs/images/objects/analogs.svg new file mode 100644 index 0000000..b88549f --- /dev/null +++ b/docs/images/objects/analogs.svg @@ -0,0 +1 @@ +channeltime \ No newline at end of file diff --git a/docs/images/objects/angles.svg b/docs/images/objects/angles.svg new file mode 100644 index 0000000..0112f13 --- /dev/null +++ b/docs/images/objects/angles.svg @@ -0,0 +1 @@ +axischanneltime \ No newline at end of file diff --git a/docs/images/objects/markers.svg b/docs/images/objects/markers.svg new file mode 100644 index 0000000..29adaca --- /dev/null +++ b/docs/images/objects/markers.svg @@ -0,0 +1 @@ +axischanneltimeXYZ axesOnes \ No newline at end of file diff --git a/docs/images/objects/rototrans.svg b/docs/images/objects/rototrans.svg new file mode 100644 index 0000000..8b41501 --- /dev/null +++ b/docs/images/objects/rototrans.svg @@ -0,0 +1 @@ +rowcoltimeRotationTranslation[0, 0, 0, 1] \ No newline at end of file diff --git a/docs/images/readme-example_files/readme-example_3_0.svg b/docs/images/readme-example_files/readme-example_3_0.svg new file mode 100644 index 0000000..09f112a --- /dev/null +++ b/docs/images/readme-example_files/readme-example_3_0.svg @@ -0,0 +1,17787 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/readme-example_files/readme-example_4_0.svg b/docs/images/readme-example_files/readme-example_4_0.svg new file mode 100644 index 0000000..3c299ae --- /dev/null +++ b/docs/images/readme-example_files/readme-example_4_0.svg @@ -0,0 +1,3741 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/readme-example_files/readme-example_5_0.svg b/docs/images/readme-example_files/readme-example_5_0.svg new file mode 100644 index 0000000..d3e4698 --- /dev/null +++ b/docs/images/readme-example_files/readme-example_5_0.svg @@ -0,0 +1,2255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/readme-example_files/readme-example_5_1.svg b/docs/images/readme-example_files/readme-example_5_1.svg new file mode 100644 index 0000000..eee4a02 --- /dev/null +++ b/docs/images/readme-example_files/readme-example_5_1.svg @@ -0,0 +1,2255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/js/charts.js b/docs/js/charts.js new file mode 100644 index 0000000..444840e --- /dev/null +++ b/docs/js/charts.js @@ -0,0 +1,264 @@ +function gridData(nRows, nCols, width) { + let out = []; + d3.range(nRows).forEach((row) => { + d3.range(nCols).forEach((col) => { + out.push({ x: width * (col % nCols), y: width * (row % nRows), width }); + }); + }); + return out; +} + +function drawMatrix(id, matrixDimensions, matrixLabels, titleLabel) { + const rectangleWidth = 40, + skewAngle = 45, + spacing = 10, + to_radian = (degree) => (degree * Math.PI) / 180, + scaling = 0.5, + xUnit = rectangleWidth * matrixDimensions[0], + yUnit = rectangleWidth * matrixDimensions[1], + zUnit = rectangleWidth * matrixDimensions[2]; + + let dimensions = { + width: document.getElementById(id).clientWidth, + margin: { left: 80, bottom: 10, right: 80, top: 10 }, + }; + dimensions.height = + dimensions.margin.top + + dimensions.margin.bottom + + xUnit + + zUnit * scaling + + 10 * spacing; + dimensions.innerHeight = + dimensions.height - dimensions.margin.top - dimensions.margin.bottom; + dimensions.innerWidth = + dimensions.width - dimensions.margin.left - dimensions.margin.right; + + const wrapper = d3 + .select(`#${id}`) + .append("svg") + .attr("width", dimensions.width) + .attr("height", dimensions.height); + + const bounds = wrapper + .append("g") + .style( + "transform", + `translate(${(dimensions.innerWidth + zUnit * scaling - yUnit) / 2}px, ${ + (dimensions.innerHeight + zUnit * scaling - xUnit + 4 * spacing) / 2 + }px)` + ); + + const matrix = bounds.append("g").attr("class", "matrix-stroke"); + + matrix + .append("g") + .selectAll(".grid") + .data(gridData(matrixDimensions[0], matrixDimensions[1], rectangleWidth)) + .enter() + .append("rect") + .attr("x", (d) => d.x) + .attr("y", (d) => d.y) + .attr("width", (d) => d.width) + .attr("height", (d) => d.width) + .style("fill", "#ECEFF1"); + + matrix + .append("g") + .attr( + "transform", + `skewX(-${skewAngle}) scale(1, ${scaling}) translate(0, -${zUnit})` + ) + .selectAll(".grid") + .data(gridData(matrixDimensions[2], matrixDimensions[1], rectangleWidth)) + .enter() + .append("rect") + .attr("x", (d) => d.x) + .attr("y", (d) => d.y) + .attr("width", (d) => d.width) + .attr("height", (d) => d.width) + .style("fill", "#CFD8DC"); + + matrix + .append("g") + .attr( + "transform", + `translate(${yUnit}, 0) skewY(-${skewAngle}) scale(${scaling}, 1)` + ) + .selectAll(".row") + .data(gridData(matrixDimensions[0], matrixDimensions[2], rectangleWidth)) + .enter() + .append("rect") + .attr("x", (d) => d.x) + .attr("y", (d) => d.y) + .attr("width", (d) => d.width) + .attr("height", (d) => d.width) + .style("fill", "#B0BEC5"); + + const lines = matrix.append("g"); + + lines + .append("line") + .attr("x1", -spacing) + .attr("x2", -spacing) + .attr("y1", 0) + .attr("y2", xUnit); + + lines + .append("line") + .attr("x1", 0) + .attr("x2", yUnit) + .attr("y1", xUnit + spacing) + .attr("y2", xUnit + spacing); + + const ySpacing = spacing * Math.sin(to_radian(skewAngle)); + const xSpacing = Math.sqrt(spacing ** 2 - ySpacing ** 2); + lines + .append("line") + .attr("x1", yUnit + xSpacing) + .attr("x2", yUnit + xSpacing + zUnit * scaling) + .attr("y1", xUnit + ySpacing) + .attr("y2", xUnit + ySpacing - zUnit * scaling); + + const texts = bounds.append("g").attr("class", "chart-text"); + + texts + .append("text") + .style("text-anchor", "end") + .attr("dx", -spacing) + .attr("x", -spacing) + .attr("y", xUnit / 2) + .text(matrixLabels[0]); + + texts + .append("text") + .style("text-anchor", "middle") + .style("alignment-baseline", "hanging") + .attr("dy", spacing) + .attr("x", yUnit / 2) + .attr("y", xUnit + spacing) + .text(matrixLabels[1]); + + texts + .append("text") + .style("text-anchor", "middle") + .style("alignment-baseline", "hanging") + .attr("dy", 10) + .attr( + "transform", + `translate(${(2 * (yUnit + xSpacing) + zUnit * scaling) / 2}, ${ + (2 * (xUnit + ySpacing) - zUnit * scaling) / 2 + }) rotate(-${skewAngle})` + ) + .text(matrixLabels[2]); + + const title = texts + .append("g") + .attr("transform", `translate(-200, ${-zUnit * scaling - 2 * spacing})`); + + title + .append("text") + .style("alignment-baseline", "ideographic") + .style("font-weight", "bold") + .attr("y", -spacing) + .text(`${titleLabel} matrix`); + + title + .append("text") + .style("alignment-baseline", "hanging") + .attr("y", -0.5 * spacing) + .text( + `Example with ${matrixDimensions[0]} ${matrixLabels[0]}, ${matrixDimensions[1]} ${matrixLabels[1]} and ${matrixDimensions[2]} ${matrixLabels[2]}:` + ); +} + +async function drawApi(id) { + const apiData = await d3.json("../api/api.json"); + + const hierarchy = d3 + .hierarchy(apiData) + .sum((d) => d.value) + .sort((a, b) => a.height - b.height || a.value - b.value); + + const spacing = 10; + const fontSize = 12; + let dimensions = { + width: document.getElementById(id).clientWidth, + margin: { left: 0, bottom: spacing, right: 0, top: spacing }, + }; + dimensions.height = (fontSize + spacing * 2) * hierarchy.value; + dimensions.innerHeight = + dimensions.height - dimensions.margin.top - dimensions.margin.bottom; + dimensions.innerWidth = + dimensions.width - dimensions.margin.left - dimensions.margin.right; + + const partition = d3 + .partition() + .size([dimensions.innerHeight, dimensions.innerWidth]) + .padding(1.5)(hierarchy); + + const colorScale = d3.scaleOrdinal([ + "#F44336", + "#E91E63", + "#2196F3", + "#4CAF50", + "#673AB7", + ]); + + const wrapper = d3 + .select(`#${id}`) + .append("svg") + .attr("width", dimensions.width) + .attr("height", dimensions.height); + + const bounds = wrapper + .append("g") + .style( + "transform", + `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)` + ); + + const cell = bounds + .selectAll("a") + .data(partition.descendants()) + .join("a") + .attr("transform", (d) => `translate(${d.y0},${d.x0})`) + .attr("href", (d) => d.data.link) + .attr("target", "_blank") + .attr("class", "cell"); + + cell + .append("rect") + .attr("width", (d) => d.y1 - d.y0) + .attr("height", (d) => d.x1 - d.x0) + .attr("fill-opacity", 0.3) + .attr("fill", (d) => { + if (!d.depth) return "#ccc"; + while (d.depth > 1) d = d.parent; + return colorScale(d.data.name); + }); + + const texts = cell + .append("g") + .attr("class", "chart-text") + .attr("transform", `translate(${spacing}, ${spacing})`); + + texts + .append("text") + .attr("dominant-baseline", "hanging") + .text((d) => d.data.name); + + // ------ Tooltip ------- + const tooltip = d3.select(`#tooltip`); + + const onMouseLeave = () => { + tooltip.style("display", "none"); + }; + + const onMouseEnter = (datum) => { + tooltip.select("#tooltip-title").text(datum.data.name); + tooltip.select("#tooltip-docstring").html(datum.data.docstring); + tooltip.style("display", "block"); + }; + + cell.on("mouseenter", onMouseEnter).on("mouseleave", onMouseLeave); +} diff --git a/docs/js/template.js b/docs/js/template.js new file mode 100644 index 0000000..39f5516 --- /dev/null +++ b/docs/js/template.js @@ -0,0 +1,34 @@ +async function renderApiTemplate(className = "template") { + const response = await fetch("../api/api.json"); + const json = await response.json(); + + const templates = document.getElementsByClassName(className); + for (const div of templates) { + if (div.innerHTML) { + setApiTemplateString(json, div); + } + } +} + +function setApiTemplateString(json, div) { + const linkToApi = div.innerHTML; + const apiObject = findApiObjectByLink(json, linkToApi); + const linkToApiP = `

Check out the ${apiObject["name"]} API reference for more details.

`; + div.innerHTML = apiObject["docstring"] + linkToApiP; +} + +function findApiObjectByLink(obj, link) { + if (obj.link === link) { + return obj; + } + let result, p; + for (p in obj) { + if (obj.hasOwnProperty(p) && typeof obj[p] === "object") { + result = findApiObjectByLink(obj[p], link); + if (result) { + return result; + } + } + } + return result; +} diff --git a/docs/nbconvert.tpl b/docs/nbconvert.tpl new file mode 100644 index 0000000..2fe89c6 --- /dev/null +++ b/docs/nbconvert.tpl @@ -0,0 +1,29 @@ +{% extends 'markdown.tpl' %} + + +{% block input %} +{% if "# ignore" not in cell.source %} +``` +{%- if 'magics_language' in cell.metadata -%} + {{ cell.metadata.magics_language}} +{%- elif 'name' in nb.metadata.get('language_info', {}) -%} + {{ nb.metadata.language_info.name }} +{%- endif %} +{{ cell.source.replace(";", "") }} +``` +{% else %} +{% endif %} +{% endblock input %} + + +{% block stream %} +
 >> {{ output.text }}
+{% endblock stream %} + +{% block data_text scoped %} +
 >> {{ output.data['text/plain'] }}
+{% endblock data_text %} + +{% block data_html scoped %} +
{{ output.data["text/html"] }}
+{% endblock data_html %} diff --git a/docs/object-creation.md b/docs/object-creation.md new file mode 100644 index 0000000..6e8f06c --- /dev/null +++ b/docs/object-creation.md @@ -0,0 +1,125 @@ +The starting point for working with Pyomeca is to create an object with one of the specific methods associated with the different classes available: + +

+ api +

+ +Pyomeca offers several ways to create these objects: from scratch, from random data, from files or from other data structures. + +## From scratch + +The first way to create a data array in Pyomeca is to directly specify the data. + +!!! example + === "Angles" +
/api/angles/#pyomeca.angles.Angles
+ + === "Markers" +
/api/markers/#pyomeca.markers.Markers
+ + === "Rototrans" +
/api/rototrans/#pyomeca.rototrans.Rototrans
+ + === "Analogs" +
/api/analogs/#pyomeca.analogs.Analogs
+ +## From random data + +We occasionally want to quickly create an object to test implementations or prototype new features. +In this case, we could simply use random numerical values. +Pyomeca offers a method for directly creating objects from random data. + +!!! Example + === "Angles" +
/api/angles/#pyomeca.angles.Angles.from_random_data
+ + === "Markers" +
/api/markers/#pyomeca.markers.Markers.from_random_data
+ + === "Rototrans" +
/api/rototrans/#pyomeca.rototrans.Rototrans.from_random_data
+ + === "Analogs" +
/api/analogs/#pyomeca.analogs.Analogs.from_random_data
+ +## From files + +Most of the time, we want to create objects from files collected during experimentation. +Pyomeca supports most of the formats used in biomechanics. + +!!! note + Pyomeca does not support a format you need? + You can inform us by opening an [issue](https://github.com/pyomeca/pyomeca/issues) or even submit a [pull request](https://github.com/pyomeca/pyomeca/pulls) to make your implementation available to the whole community! + +=== "c3d" + !!! Example + === "Markers" +
/api/markers/#pyomeca.markers.Markers.from_c3d
+ + === "Analogs" +
/api/analogs/#pyomeca.analogs.Analogs.from_c3d
+ +=== "csv" + !!! Example + === "Markers" +
/api/markers/#pyomeca.markers.Markers.from_csv
+ + === "Analogs" +
/api/analogs/#pyomeca.analogs.Analogs.from_csv
+ +=== "excel" + !!! Example + === "Markers" +
/api/markers/#pyomeca.markers.Markers.from_excel
+ + === "Analogs" +
/api/analogs/#pyomeca.analogs.Analogs.from_excel
+ +=== "mot" + !!! Example +
/api/analogs/#pyomeca.analogs.Analogs.from_mot
+ +=== "trc" + !!! Example +
/api/markers/#pyomeca.markers.Markers.from_trc
+ +=== "sto" + !!! Example +
/api/analogs/#pyomeca.analogs.Analogs.from_sto
+ +## From other data structures + +We often have to switch between different representations of the same data. +Pyomeca implements different matrix manipulation routines such as getting Euler angles or a marker to/from a rototranslation matrix. + +### Angles & Rototrans + +!!! Example + === "Angles from Rototrans" +
/api/angles/#pyomeca.angles.Angles.from_rototrans
+ + === "Rototrans from Angles" +
/api/rototrans/#pyomeca.rototrans.Rototrans.from_euler_angles
+ +### Markers & Rototrans + +!!! Example + ===! "Markers from Rototrans" +
/api/markers/#pyomeca.markers.Markers.from_rototrans
+ + === "Rototrans from Markers" +
/api/rototrans/#pyomeca.rototrans.Rototrans.from_markers
+ +### Processed Rototrans + +!!! Example + ===! "Rototrans from a transposed Rototrans" +
/api/rototrans/#pyomeca.rototrans.Rototrans.from_transposed_rototrans
+ + === "Rototrans from an averaged Rototrans" +
/api/rototrans/#pyomeca.rototrans.Rototrans.from_averaged_rototrans
+ + + diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..380fc06 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,74 @@ +## Working with labelled multidimensional arrays + +Pyomeca introduces a concise interface to read, analyse, visualize and plot biomechanical data. + +Such data are typically *multi-dimensional*, such as joint angles with associated axes, degrees of freedom and time frames. + + + +[NumPy](https:numpy.org) is the fundamental package for multi-dimensional computing with Python. +While NumPy provides an efficient data structure and an intuitive interface, biomechanical datasets typically contain more than just raw numbers and have labels which encode how the array values map to different dimensions such as axes, degrees of freedom, channels or time frames. + +Pyomeca is built upon and extends the core strengths of [xarray](http://xarray.pydata.org/en/stable/index.html), which keeps tracks of labels and provides a powerful and concise interface which makes it easy to: + +- Apply any operations over dimensions by name (`array.sum(dim="time")`) instead of an arbitrary axis (`array.sum(axis=2)`). +- Select values by labels (`array.sel(axis="x")` or `emg.sel(channel="biceps")`). +- Vectorize computation across multiple dimensions. +- Use the [split-apply-combine](https://vita.had.co.nz/papers/plyr.pdf) paradigm, for example: `emg.groupby("channel").mean()` or any custom function: `emg.groupby('channel').map(lambda x: x - x.mean())`). +- Keep track of metadata in the `array.attrs` Python dictionary (`array.attrs["rate"]`). +- Extent the xarray interface with domain specific functionalities with custom accessors on xarray objects. In pyomeca, the biomechanics specific functions are registered under the `meca` name space (`array.meca`). + +Working with labels makes it much easier to work with multi-dimensional arrays as you do not have to keep track of the order of the dimensions or insert dummy dimensions to align arrays. +This allows for a more intuitive, more concise, and less error-prone developer experience. + +!!! note + As the underlying data structure is still a NumPy array, NumPy functions (`np.abs(array)`) and indexing (`array[:, 0, 1]`) work out of the box. + +By leveraging xarray data structures, Pyomeca inherits their features such as built-in [interpolation](http://xarray.pydata.org/en/stable/interpolation.html), [computation](http://xarray.pydata.org/en/stable/computation.html), [GroupBy](http://xarray.pydata.org/en/stable/groupby.html), [data wrangling](http://xarray.pydata.org/en/stable/combining.html), [parallel computing](http://xarray.pydata.org/en/stable/dask.html) and [plotting](http://xarray.pydata.org/en/stable/plotting.html). + +!!! info "Extending xarray" + Xarray is designed as a general-purpose library and tries to avoid including domain specific functionalities. + But inevitably, the need for more domain specific logic arises. + That's why Pyomeca and [dozens of other scientific packages](http://xarray.pydata.org/en/stable/related-projects.html) extend xarray. + + Extending data structure in Python is usually achieved with class inheritance. + However inheritance is not very robust for large class such as `xarray.DataArray`. + To add domain specific functionality, pyomeca follows xarray developers' recommendations and use a custom "accessor". + For more information, you can check out the [xarray documentation](http://xarray.pydata.org/en/stable/internals.html#extending-xarray). + +## Core functionalities + +Pyomeca has four data structures built upon [xarray](http://xarray.pydata.org/en/stable/index.html). +Each structure is associated with a specific biomechanical data type and has specialized functionalities: + +| Class | Dimensions | Description | +|-------------------------|-------------------------------------|------------------------------------------------------------------------| +| [`Analogs`](/api/analogs/#pyomeca.analogs.Analogs) | `("channel", "time")` | Generic signals such as EMGs, force signals or any other analog signals | +| [`Angles`](/api/angles/#pyomeca.angles.Angles) | `("axis", "channel", "time")` | Joint angles | +| [`Markers`](/api/markers/#pyomeca.markers.Markers) | `("axis", "channel", "time")` | Skin marker positions | +| [`Rototrans`](/api/rototrans/#pyomeca.rototrans.Rototrans) | `("row", "col", "time")` | Rototranslation matrix | + +While there are technically dozens of functions in pyomeca one can generally group them into two distinct categories: + +1. [Object creation](https://pyomeca.github.io/object-creation/) with the `from_*` methods. For example, if you want to define a marker array from a csv file: `markers = Markers.from_csv(...)`. +2. [Data processing](https://pyomeca.github.io/data-processing/) with the `meca` array accessor. For example, to low-pass filter our previous markers: `markers.meca.low_pass(...)`. + +!!! note + Check out the API reference to see the parameters, use cases and examples associated with each function. + +You can explore all of pyomeca's public API on the following interactive visualization. +Hover the mouse over any block to display a short description with some examples + and click to jump to the corresponding API reference. + +
+
+

+

+
+
+ + + + diff --git a/environment.yml b/environment.yml index 3b89ab4..b3fcd32 100644 --- a/environment.yml +++ b/environment.yml @@ -1,19 +1,12 @@ -# create a dev conda environment with: `make create_env` or `conda env create -f environment.yml` name: pyomeca channels: -- conda-forge -- default + - conda-forge dependencies: -- python -- numpy -- pandas -- matplotlib -- scipy -- ezc3d -- pytest -- pytest-cov -- black -- xlrd -- pip -- pip: - - -e . + - python + - numpy > 1.18 + - scipy + - xarray + - ezc3d >= 1.3.2 + - matplotlib + - bottleneck + - xlrd diff --git a/examples/fileio.py b/examples/fileio.py deleted file mode 100644 index 6d6163e..0000000 --- a/examples/fileio.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Example script for file IO -""" - -from pathlib import Path - -from pyomeca import Markers3d, Analogs3d - -# Path to data -DATA_FOLDER = Path("..") / "tests" / "data" -MARKERS_CSV = DATA_FOLDER / "markers.csv" -MARKERS_ANALOGS_C3D = DATA_FOLDER / "markers_analogs.c3d" -ANALOGS_CSV = DATA_FOLDER / "analogs.csv" - -# read 11 first markers of a csv file -markers_1 = Markers3d.from_csv( - MARKERS_CSV, - first_row=5, - first_column=2, - header=2, - idx=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - prefix=":", -) - -# mean of 1st and 4th markers of a csv file -markers_2 = Markers3d.from_csv( - MARKERS_CSV, - first_row=5, - first_column=2, - header=2, - idx=[[0, 1, 2], [0, 4, 2]], - prefix=":", -) - -# get markers by names in a csv file -markers_3 = Markers3d.from_csv( - MARKERS_CSV, - first_row=5, - first_column=2, - header=2, - names=["CLAV_post", "PSISl", "STERr", "CLAV_post"], - prefix=":", -) - -# write a csv file from a Markers3d types -markers_3.to_csv("../Misc/mtest.csv", header=False) - -# read 4 first markers of a c3d file -markers_4 = Markers3d.from_c3d(MARKERS_ANALOGS_C3D, idx=[0, 1, 2, 3]) - -# get 5 first analogs of a csv file -analogs_1 = Analogs3d.from_csv( - ANALOGS_CSV, first_row=5, first_column=2, header=3, idx=[[0, 1, 2], [0, 4, 2]] -) - -# get analogs by names in a c3d file -analogs_2 = Analogs3d.from_c3d( - MARKERS_ANALOGS_C3D, - prefix=":", - names=["Delt_ant.EMG1", "Subscap.EMG11", "Triceps.EMG5", "Gd_dors.IM EMG13"], -) - -# write analogs to a csv file without header -analogs_2.to_csv("../Misc/atest.csv", header=True) diff --git a/examples/signal_processing.py b/examples/signal_processing.py deleted file mode 100644 index 86d6c46..0000000 --- a/examples/signal_processing.py +++ /dev/null @@ -1,255 +0,0 @@ -"""" -Signal processing examples in pyomeca -""" - -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np - -from pyomeca import Analogs3d - -# Path to data -DATA_FOLDER = Path("..") / "tests" / "data" -MARKERS_ANALOGS_C3D = DATA_FOLDER / "markers_analogs.c3d" - -# read an emg from a c3d file -a = Analogs3d.from_c3d(MARKERS_ANALOGS_C3D, names=["Delt_ant.EMG1"]) -a.plot() -plt.show() - -# --- Pyomeca types method implementation - -# every function described below are implemented as method in pyomeca types and can be chained: -amp_, freqs_ = ( - a.rectify() - .center() - .moving_rms(window_size=100) - .moving_average(window_size=100) - .moving_median(window_size=100 - 1) - .low_pass(freq=a.get_rate, order=2, cutoff=5) - .band_pass(freq=a.get_rate, order=4, cutoff=[10, 200]) - .band_stop(freq=a.get_rate, order=4, cutoff=[49.9, 50.1]) - .high_pass(freq=a.get_rate, order=4, cutoff=30) - .time_normalization() - .normalization() - .fft(freq=a.get_rate) -) - -# --- Rectify and center -b = a + 2 * a.mean() - -_, ax = plt.subplots(nrows=1, ncols=1) -b.plot(ax=ax, label="raw") -b.center().plot(ax=ax, fmt="b-", alpha=0.7, label="centered") -b.rectify().plot(ax=ax, fmt="g-", alpha=0.7, label="rectified (abs)") -ax.legend() - -ax.set_title("Rectify and center") -ax.legend() -plt.show() - -# --- Moving rms -WINDOW_SIZE = 100 - -mv_rms = a.moving_rms(window_size=WINDOW_SIZE) - -_, ax = plt.subplots(nrows=1, ncols=1) - -a.plot(ax=ax, fmt="k-", label="raw") -mv_rms.plot(ax=ax, fmt="r-", lw=2, label="moving rms") - -ax.set_title(f"Moving RMS (window = {WINDOW_SIZE})") -ax.legend() -plt.show() - -# --- Moving average -b = Analogs3d(a.moving_rms(window_size=10)) - -mv_mu = b.moving_average(window_size=WINDOW_SIZE) - -_, ax = plt.subplots(nrows=1, ncols=1) - -b.plot(ax=ax, fmt="k-", label="raw") -mv_mu.plot(ax=ax, fmt="b-", lw=2, label="moving average") - -ax.set_title(f"Moving average (window = {WINDOW_SIZE})") -ax.legend() -plt.show() - -# --- Moving median (sharper response to abrupt changes than the moving average) -mv_med = b.moving_median(window_size=WINDOW_SIZE - 1) - -_, ax = plt.subplots(nrows=1, ncols=1) - -b.plot(ax=ax, fmt="k-", label="raw") -mv_rms.plot(ax=ax, fmt="r-", lw=2, label="moving rms") -mv_mu.plot(ax=ax, fmt="g-", lw=2, label="moving average") -mv_med.plot(ax=ax, fmt="m-", lw=2, label="moving median") - -ax.set_title(f"Comparison of moving methods (window = {WINDOW_SIZE - 1})") -ax.legend() -plt.show() - -# --- Low-pass filter -freq = 100 -t = np.arange(0, 1, 0.01) -w = 2 * np.pi * 1 -y = np.sin(w * t) + 0.1 * np.sin(10 * w * t) -y = Analogs3d(y.reshape(1, 1, -1)) - -low_pass = y.low_pass(freq=freq, order=2, cutoff=5) - -_, ax = plt.subplots(nrows=1, ncols=1) - -y.plot(ax=ax, fmt="k-", label="raw") -low_pass.plot(ax=ax, fmt="r-", label="low-pass @ 5Hz") - -ax.set_title("Low-pass Butterworth filter") -ax.legend() -plt.show() - -# --- Band-pass filter -band_pass = a.band_pass(freq=a.get_rate, order=4, cutoff=[10, 200]) - -_, ax = plt.subplots(nrows=1, ncols=1) - -a.plot(ax=ax, fmt="k-", label="raw") -band_pass.plot(ax=ax, fmt="r-", alpha=0.7, label="band-pass @ 10-200Hz") - -ax.set_title("Band-pass Butterworth filter") -ax.legend() -plt.show() - -# --- Band-stop filter (useful to remove the 50Hz noise for example) -band_stop = a.band_stop(freq=a.get_rate, order=2, cutoff=[49.9, 50.1]) - -_, ax = plt.subplots(nrows=1, ncols=1) - -a.plot(ax=ax, fmt="k-", label="raw") -band_stop.plot(ax=ax, fmt="r-", alpha=0.7, label="band-stop @ 49.9-50.1Hz") - -ax.set_title("Band-stop Butterworth filter") -ax.legend() -plt.show() - -# --- High-pass filter -high_pass = a.high_pass(freq=a.get_rate, order=2, cutoff=100) - -_, ax = plt.subplots(nrows=1, ncols=1) - -a.plot(ax=ax, fmt="k-", label="raw") -high_pass.plot(ax=ax, fmt="r-", alpha=0.7, label="high-pass @ 30Hz") - -ax.set_title("High-pass Butterworth filter") -ax.legend() -plt.show() - -# --- Time normalization - -time_normalized = a.time_normalization(time_vector=np.linspace(0, 100, 101)) - -# --- Amplitude normalization - -amp_normalized = a.normalization() - -# --- EMG: a complete example - -emg = ( - a.band_pass(freq=a.get_rate, order=4, cutoff=[10, 425]) - .center() - .rectify() - .low_pass(freq=a.get_rate, order=4, cutoff=5) - .normalization() - .time_normalization() -) - -_, ax = plt.subplots(nrows=2, ncols=1) - -a.plot(ax=ax[0], fmt="k-") -ax[0].set_title("Raw data") - -emg.plot(ax=ax[1], fmt="r-") -ax[1].set_title("Processed data") - -plt.show() - -# --- FFT - -# fft on raw data -amp, freqs = y.fft(freq=freq) -# compare with low-pass filtered data -amp_filtered, freqs_filtered = low_pass.fft(freq=freq) - -_, ax = plt.subplots(nrows=2, ncols=1) - -y.plot(ax=ax[0], fmt="k-", label="raw") -low_pass.plot(ax=ax[0], fmt="r-", alpha=0.7, label="low-pass @ 5Hz") -ax[0].set_title("Temporal domain") - -ax[1].plot(freqs, amp.squeeze(), "k-", label="raw") -ax[1].plot(freqs_filtered, amp_filtered.squeeze(), "r-", label="low-pass @ 5Hz") -ax[1].set_title("Frequency domain") - -ax[1].legend() -plt.show() - -# fft on real data -emg_without_low_pass = ( - a.band_pass(freq=a.get_rate, order=4, cutoff=[10, 425]).center().rectify() -) - -emg_with_a_low_pass = ( - a.band_pass(freq=a.get_rate, order=4, cutoff=[10, 425]) - .center() - .rectify() - .low_pass(freq=a.get_rate, order=4, cutoff=5) -) - -amp_a, freqs_a = emg_without_low_pass.fft(freq=freq) -amp_a_filtered, freqs_a_filtered = emg_with_a_low_pass.fft(freq=freq) - -_, ax = plt.subplots(nrows=2, ncols=1) - -emg_without_low_pass.plot(ax=ax[0], fmt="k-", label="raw", alpha=0.7) -emg_with_a_low_pass.plot(ax=ax[0], fmt="r-", label="low-pass @ 5Hz", alpha=0.7) -ax[0].set_title("Temporal domain") - -ax[1].plot(freqs_a, amp_a.squeeze(), "k-", label="raw") -ax[1].plot(freqs_a_filtered, amp_a_filtered.squeeze(), "r-", label="low-pass @ 5Hz") -ax[1].set_xlim(-2, 10) -ax[1].set_title("Frequency domain") -ax[1].legend() - -plt.show() - -# --- Norm - -b = Analogs3d.from_c3d(MARKERS_ANALOGS_C3D, idx=[0, 1, 2, 3, 4, 5]) - -# offset on one second, then compute the norm -norm = b.center(mu=np.nanmean(b[..., : int(b.get_rate)], axis=-1), axis=-1).norm() - -plt.plot(norm) -plt.show() - -# --- Onset detection -two_norm = np.hstack((norm, norm / 4)) -two_norm = Analogs3d(two_norm.reshape(1, 1, -1)) - -# threshold = mean during the first second -idx = two_norm.detect_onset( - threshold=np.nanmean(two_norm[..., : int(b.get_rate)]), - above=int(b.get_rate) / 2, - below=3, - threshold2=np.nanmean(two_norm[..., : int(b.get_rate)]) * 2, - above2=5, -) - -_, ax = plt.subplots(nrows=1, ncols=1) -two_norm.plot(ax=ax) -for (inf, sup) in idx: - ax.axvline(x=inf, color="r", lw=2, ls="--") - ax.axvline(x=sup, color="r", lw=2, ls="--") -ax.set_title("Onset detection") -plt.show() diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..3ea14e0 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,54 @@ +site_name: Pyomeca +site_description: An Open-Source Framework for Biomechanical Analysis +site_author: Romain Martinez +site_url: https://pyomeca.github.io + +theme: + name: material + palette: + primary: blue grey + accent: teal + logo: images/logo.svg + favicon: images/favicon.ico +nav: + - Home: index.md + - Overview: overview.md + - Getting Started: getting-started.md + - User Guide: + - Object creation: object-creation.md + - Data processing: data-processing.md + - About: about.md + - API reference: + Analogs: api/analogs.md + Angles: api/angles.md + Markers: api/markers.md + DataArray_accessor: api/dataarray_accessor.md + Rototrans: api/rototrans.md + +markdown_extensions: + - admonition + - footnotes + - codehilite + - pymdownx.superfences + - pymdownx.tabbed + +plugins: + - search + - mkdocstrings: + watch: + - pyomeca + - minify: + minify_html: true + +repo_name: pyomeca/pyomeca +repo_url: https://github.com/pyomeca/pyomeca + +copyright: Copyright © 2019 Romain Martinez + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/romainmartinez + +extra_css: + - custom.css diff --git a/notebooks/getting-started.ipynb b/notebooks/getting-started.ipynb new file mode 100644 index 0000000..28d0e0f --- /dev/null +++ b/notebooks/getting-started.ipynb @@ -0,0 +1,39899 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# ignore_cell\n", + "# You can safely ignore this cell\n", + "\n", + "# add pyomeca to local path (to make it usable in binder server)\n", + "import sys\n", + "\n", + "sys.path.append(\"..\")\n", + "\n", + "# Plot style\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import set_matplotlib_formats\n", + "\n", + "set_matplotlib_formats(\"svg\")\n", + "mpl.rcParams[\"axes.spines.right\"] = False\n", + "mpl.rcParams[\"axes.spines.top\"] = False\n", + "plt.style.use(\"seaborn-ticks\")\n", + "%matplotlib inline\n", + "# %load_ext lab_black" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation\n", + "\n", + "Before you can use pyomeca, you will need to get it installed.\n", + "Pyomeca itself is a pure Python package, but its dependencies are not.\n", + "The easiest way to get everything installed is to use [conda](https://conda.io/en/latest/miniconda.html).\n", + "\n", + "To install pyomeca with its recommended dependencies using the conda command line tool:\n", + "\n", + "```bash\n", + "conda install -c conda-forge pyomeca\n", + "```\n", + "Now that you have installed pyomeca, you should be able to import it:\n", + "\n", + "```python\n", + "import pyomeca\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "!!! note\n", + " Want to test pyomeca from your browser and without installing anything?\n", + " Try out our binder server: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyomeca/pyomeca/master?filepath=notebooks)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quick overview\n", + "\n", + "Here is a short introduction to xarray and pyomeca, geared mainly for new users.\n", + "You should be able to follow along and complete this short example in about 10 minutes.\n", + "\n", + "We will carry out common tasks in biomechanics, including reading files, manipulating and processing data, making figures and writing files." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Object creation\n", + "\n", + "Let's begin by creating a biomechanical data structure with pyomeca.\n", + "In this tutorial, we will analyze the skin marker data available in this [`c3d file`](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers_analogs.c3d).\n", + "\n", + "Pyomeca provides the `from_c3d` function to read `c3d` files.\n", + "As we want to analyse markers data, we will use the `Markers` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from pyomeca import Markers\n", + "\n", + "data_path = \"../tests/data/markers_analogs.c3d\"\n", + "markers = Markers.from_c3d(data_path, prefix_delimiter=\":\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Make sure to always have a check on the data after reading it.\n", + "\n", + "When used in [Jupyter notebooks](https://jupyter.org/), data can be explored interactively.\n", + "A standard text representation is available otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "Show/Hide data repr\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Show/Hide attributes\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
xarray.DataArray
'markers'
  • axis: 4
  • channel: 51
  • time: 580
  • 44.16 44.17 44.16 44.17 44.17 44.19 44.2 ... 1.0 1.0 1.0 1.0 1.0 1.0
    array([[[  44.16278839,   44.16666412,   44.16487122, ...,\n",
    +       "           99.22426605,   99.24201965,   99.25963593],\n",
    +       "        [  32.57229614,   32.57104111,   32.56489563, ...,\n",
    +       "           87.51286316,   87.52822876,   87.54118347],\n",
    +       "        [ -93.72181702,  -93.72447968,  -93.72324371, ...,\n",
    +       "          -41.1590271 ,  -41.14812851,  -41.12734985],\n",
    +       "        ...,\n",
    +       "        [ 562.26068115,  562.41027832,  562.56695557, ...,\n",
    +       "          625.63555908,  625.98504639,  626.25811768],\n",
    +       "        [ 568.24200439,  568.37792969,  568.49249268, ...,\n",
    +       "          624.18139648,  624.51190186,  624.78894043],\n",
    +       "        [ 568.44470215,  568.52038574,  568.59216309, ...,\n",
    +       "          623.09222412,  623.44036865,  623.75152588]],\n",
    +       "\n",
    +       "       [[-276.86193848, -276.86169434, -276.86407471, ...,\n",
    +       "         -259.15292358, -259.16690063, -259.17092896],\n",
    +       "        [-243.14048767, -243.14073181, -243.13331604, ...,\n",
    +       "         -225.44718933, -225.45556641, -225.46226501],\n",
    +       "        [ 124.78598022,  124.78731537,  124.78870392, ...,\n",
    +       "          141.820755  ,  141.80741882,  141.80308533],\n",
    +       "        ...,\n",
    +       "        [ 638.35144043,  638.4241333 ,  638.50653076, ...,\n",
    +       "          592.00372314,  592.15686035,  592.27819824],\n",
    +       "        [ 626.79144287,  626.86114502,  626.90710449, ...,\n",
    +       "          584.15661621,  584.27709961,  584.38830566],\n",
    +       "        [ 651.37927246,  651.45532227,  651.49957275, ...,\n",
    +       "          610.59655762,  610.72821045,  610.84472656]],\n",
    +       "\n",
    +       "       [[ 675.69683838,  675.69873047,  675.6986084 , ...,\n",
    +       "          903.97650146,  903.96801758,  903.980896  ],\n",
    +       "        [ 676.57452393,  676.58099365,  676.57720947, ...,\n",
    +       "          904.61694336,  904.61645508,  904.63104248],\n",
    +       "        [ 674.27874756,  674.27947998,  674.28033447, ...,\n",
    +       "          902.77349854,  902.77819824,  902.78143311],\n",
    +       "        ...,\n",
    +       "        [  81.34425354,   81.32899475,   81.2776413 , ...,\n",
    +       "           53.45215607,   53.57727814,   53.72877121],\n",
    +       "        [ 110.84020996,  110.81329346,  110.76582336, ...,\n",
    +       "           83.92819214,   84.07093048,   84.20204163],\n",
    +       "        [ 129.69673157,  129.6789856 ,  129.62939453, ...,\n",
    +       "           99.39131165,   99.52735138,   99.68258667]],\n",
    +       "\n",
    +       "       [[   1.        ,    1.        ,    1.        , ...,\n",
    +       "            1.        ,    1.        ,    1.        ],\n",
    +       "        [   1.        ,    1.        ,    1.        , ...,\n",
    +       "            1.        ,    1.        ,    1.        ],\n",
    +       "        [   1.        ,    1.        ,    1.        , ...,\n",
    +       "            1.        ,    1.        ,    1.        ],\n",
    +       "        ...,\n",
    +       "        [   1.        ,    1.        ,    1.        , ...,\n",
    +       "            1.        ,    1.        ,    1.        ],\n",
    +       "        [   1.        ,    1.        ,    1.        , ...,\n",
    +       "            1.        ,    1.        ,    1.        ],\n",
    +       "        [   1.        ,    1.        ,    1.        , ...,\n",
    +       "            1.        ,    1.        ,    1.        ]]])
    • axis
      (axis)
      <U4
      'x' 'y' 'z' 'ones'
      array(['x', 'y', 'z', 'ones'], dtype='<U4')
    • channel
      (channel)
      <U14
      'gauche_ext' ... 'LATH'
      array(['gauche_ext', 'gauche_int', 'droite_int', 'droite_ext', 'avant_gauche',\n",
      +       "       'avant_droit', 'arriere_droit', 'arriere_gauche', 'ASISr', 'ASISl',\n",
      +       "       'PSISr', 'PSISl', 'STERr', 'STERl', 'STER', 'XIPH', 'T1', 'T10',\n",
      +       "       'CLAV_SC', 'CLAVm', 'CLAV_ant', 'CLAV_post', 'CLAVl', 'CLAV_AC',\n",
      +       "       'ACRO_tip', 'SCAP_AA', 'SCAPl', 'SCAPm', 'SCAP_CP', 'SCAP_RS',\n",
      +       "       'SCAP_SA', 'SCAP_IA', 'DELT', 'ARMl', 'ARMm', 'ARMp_up', 'ARMp_do',\n",
      +       "       'EPICl', 'EPICm', 'LARMm', 'LARMl', 'LARM_elb', 'LARM_ant', 'STYLr',\n",
      +       "       'STYLr_up', 'STYLu', 'WRIST', 'INDEX', 'LASTC', 'MEDH', 'LATH'],\n",
      +       "      dtype='<U14')
    • time
      (time)
      float64
      0.0 0.01 0.02 ... 5.77 5.78 5.79
      array([0.  , 0.01, 0.02, ..., 5.77, 5.78, 5.79])
  • first_frame :
    0
    last_frame :
    579
    rate :
    100.0
    units :
    mm
" + ], + "text/plain": [ + "\n", + "array([[[ 44.16278839, 44.16666412, 44.16487122, ...,\n", + " 99.22426605, 99.24201965, 99.25963593],\n", + " [ 32.57229614, 32.57104111, 32.56489563, ...,\n", + " 87.51286316, 87.52822876, 87.54118347],\n", + " [ -93.72181702, -93.72447968, -93.72324371, ...,\n", + " -41.1590271 , -41.14812851, -41.12734985],\n", + " ...,\n", + " [ 562.26068115, 562.41027832, 562.56695557, ...,\n", + " 625.63555908, 625.98504639, 626.25811768],\n", + " [ 568.24200439, 568.37792969, 568.49249268, ...,\n", + " 624.18139648, 624.51190186, 624.78894043],\n", + " [ 568.44470215, 568.52038574, 568.59216309, ...,\n", + " 623.09222412, 623.44036865, 623.75152588]],\n", + "\n", + " [[-276.86193848, -276.86169434, -276.86407471, ...,\n", + " -259.15292358, -259.16690063, -259.17092896],\n", + " [-243.14048767, -243.14073181, -243.13331604, ...,\n", + " -225.44718933, -225.45556641, -225.46226501],\n", + " [ 124.78598022, 124.78731537, 124.78870392, ...,\n", + " 141.820755 , 141.80741882, 141.80308533],\n", + " ...,\n", + " [ 638.35144043, 638.4241333 , 638.50653076, ...,\n", + " 592.00372314, 592.15686035, 592.27819824],\n", + " [ 626.79144287, 626.86114502, 626.90710449, ...,\n", + " 584.15661621, 584.27709961, 584.38830566],\n", + " [ 651.37927246, 651.45532227, 651.49957275, ...,\n", + " 610.59655762, 610.72821045, 610.84472656]],\n", + "\n", + " [[ 675.69683838, 675.69873047, 675.6986084 , ...,\n", + " 903.97650146, 903.96801758, 903.980896 ],\n", + " [ 676.57452393, 676.58099365, 676.57720947, ...,\n", + " 904.61694336, 904.61645508, 904.63104248],\n", + " [ 674.27874756, 674.27947998, 674.28033447, ...,\n", + " 902.77349854, 902.77819824, 902.78143311],\n", + " ...,\n", + " [ 81.34425354, 81.32899475, 81.2776413 , ...,\n", + " 53.45215607, 53.57727814, 53.72877121],\n", + " [ 110.84020996, 110.81329346, 110.76582336, ...,\n", + " 83.92819214, 84.07093048, 84.20204163],\n", + " [ 129.69673157, 129.6789856 , 129.62939453, ...,\n", + " 99.39131165, 99.52735138, 99.68258667]],\n", + "\n", + " [[ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " ...,\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ]]])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4)>\n", + "array([753.43908691, 75.9487381 , 187.7590332 , 1. ])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'ASISl'\n", + " time float64 0.0\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm" + ], + "text/plain": [ + "\n", + "array([753.43908691, 75.9487381 , 187.7590332 , 1. ])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4)>\n", + "array([753.43908691, 75.9487381 , 187.7590332 , 1. ])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'ASISl'\n", + " time float64 0.0\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm" + ], + "text/plain": [ + "\n", + "array([753.43908691, 75.9487381 , 187.7590332 , 1. ])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4)>\n", + "array([753.43908691, 75.9487381 , 187.7590332 , 1. ])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'ASISl'\n", + " time float64 0.0\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm" + ], + "text/plain": [ + "\n", + "array([753.43908691, 75.9487381 , 187.7590332 , 1. ])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4, channel: 51, time: 580)>\n", + "array([[[ 44.16278839, 44.16666412, 44.16487122, ...,\n", + " 99.22426605, 99.24201965, 99.25963593],\n", + " [ 32.57229614, 32.57104111, 32.56489563, ...,\n", + " 87.51286316, 87.52822876, 87.54118347],\n", + " [ -93.72181702, -93.72447968, -93.72324371, ...,\n", + " -41.1590271 , -41.14812851, -41.12734985],\n", + " ...,\n", + " [ 562.26068115, 562.41027832, 562.56695557, ...,\n", + " 625.63555908, 625.98504639, 626.25811768],\n", + " [ 568.24200439, 568.37792969, 568.49249268, ...,\n", + " 624.18139648, 624.51190186, 624.78894043],\n", + " [ 568.44470215, 568.52038574, 568.59216309, ...,\n", + " 623.09222412, 623.44036865, 623.75152588]],\n", + "\n", + " [[-276.86193848, -276.86169434, -276.86407471, ...,\n", + " -259.15292358, -259.16690063, -259.17092896],\n", + " [-243.14048767, -243.14073181, -243.13331604, ...,\n", + " -225.44718933, -225.45556641, -225.46226501],\n", + " [ 124.78598022, 124.78731537, 124.78870392, ...,\n", + " 141.820755 , 141.80741882, 141.80308533],\n", + " ...,\n", + " [ 638.35144043, 638.4241333 , 638.50653076, ...,\n", + " 592.00372314, 592.15686035, 592.27819824],\n", + " [ 626.79144287, 626.86114502, 626.90710449, ...,\n", + " 584.15661621, 584.27709961, 584.38830566],\n", + " [ 651.37927246, 651.45532227, 651.49957275, ...,\n", + " 610.59655762, 610.72821045, 610.84472656]],\n", + "\n", + " [[ 675.69683838, 675.69873047, 675.6986084 , ...,\n", + " 903.97650146, 903.96801758, 903.980896 ],\n", + " [ 676.57452393, 676.58099365, 676.57720947, ...,\n", + " 904.61694336, 904.61645508, 904.63104248],\n", + " [ 674.27874756, 674.27947998, 674.28033447, ...,\n", + " 902.77349854, 902.77819824, 902.78143311],\n", + " ...,\n", + " [ 81.34425354, 81.32899475, 81.2776413 , ...,\n", + " 53.45215607, 53.57727814, 53.72877121],\n", + " [ 110.84020996, 110.81329346, 110.76582336, ...,\n", + " 83.92819214, 84.07093048, 84.20204163],\n", + " [ 129.69673157, 129.6789856 , 129.62939453, ...,\n", + " 99.39131165, 99.52735138, 99.68258667]],\n", + "\n", + " [[ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " ...,\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ]]])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " * channel (channel) <U14 'gauche_ext' 'gauche_int' ... 'MEDH' 'LATH'\n", + " * time (time) float64 0.0 0.01 0.02 0.03 0.04 ... 5.75 5.76 5.77 5.78 5.79\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm\n", + " description: Skin marker positions recorded in Montreal.\n", + " participant_id: 12" + ], + "text/plain": [ + "\n", + "array([[[ 44.16278839, 44.16666412, 44.16487122, ...,\n", + " 99.22426605, 99.24201965, 99.25963593],\n", + " [ 32.57229614, 32.57104111, 32.56489563, ...,\n", + " 87.51286316, 87.52822876, 87.54118347],\n", + " [ -93.72181702, -93.72447968, -93.72324371, ...,\n", + " -41.1590271 , -41.14812851, -41.12734985],\n", + " ...,\n", + " [ 562.26068115, 562.41027832, 562.56695557, ...,\n", + " 625.63555908, 625.98504639, 626.25811768],\n", + " [ 568.24200439, 568.37792969, 568.49249268, ...,\n", + " 624.18139648, 624.51190186, 624.78894043],\n", + " [ 568.44470215, 568.52038574, 568.59216309, ...,\n", + " 623.09222412, 623.44036865, 623.75152588]],\n", + "\n", + " [[-276.86193848, -276.86169434, -276.86407471, ...,\n", + " -259.15292358, -259.16690063, -259.17092896],\n", + " [-243.14048767, -243.14073181, -243.13331604, ...,\n", + " -225.44718933, -225.45556641, -225.46226501],\n", + " [ 124.78598022, 124.78731537, 124.78870392, ...,\n", + " 141.820755 , 141.80741882, 141.80308533],\n", + " ...,\n", + " [ 638.35144043, 638.4241333 , 638.50653076, ...,\n", + " 592.00372314, 592.15686035, 592.27819824],\n", + " [ 626.79144287, 626.86114502, 626.90710449, ...,\n", + " 584.15661621, 584.27709961, 584.38830566],\n", + " [ 651.37927246, 651.45532227, 651.49957275, ...,\n", + " 610.59655762, 610.72821045, 610.84472656]],\n", + "\n", + " [[ 675.69683838, 675.69873047, 675.6986084 , ...,\n", + " 903.97650146, 903.96801758, 903.980896 ],\n", + " [ 676.57452393, 676.58099365, 676.57720947, ...,\n", + " 904.61694336, 904.61645508, 904.63104248],\n", + " [ 674.27874756, 674.27947998, 674.28033447, ...,\n", + " 902.77349854, 902.77819824, 902.78143311],\n", + " ...,\n", + " [ 81.34425354, 81.32899475, 81.2776413 , ...,\n", + " 53.45215607, 53.57727814, 53.72877121],\n", + " [ 110.84020996, 110.81329346, 110.76582336, ...,\n", + " 83.92819214, 84.07093048, 84.20204163],\n", + " [ 129.69673157, 129.6789856 , 129.62939453, ...,\n", + " 99.39131165, 99.52735138, 99.68258667]],\n", + "\n", + " [[ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " ...,\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ],\n", + " [ 1. , 1. , 1. , ...,\n", + " 1. , 1. , 1. ]]])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4, time: 6)>\n", + "array([[ 54.16278839, 54.16666412, 54.16487122, 54.16558075,\n", + " 54.17311096, 54.18517685],\n", + " [-266.86193848, -266.86169434, -266.86407471, -266.86123657,\n", + " -266.85812378, -266.85818481],\n", + " [ 685.69683838, 685.69873047, 685.6986084 , 685.69775391,\n", + " 685.7041626 , 685.69592285],\n", + " [ 11. , 11. , 11. , 11. ,\n", + " 11. , 11. ]])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'\n", + " * time (time) float64 0.0 0.01 0.02 0.03 0.04 0.05" + ], + "text/plain": [ + "\n", + "array([[ 54.16278839, 54.16666412, 54.16487122, 54.16558075,\n", + " 54.17311096, 54.18517685],\n", + " [-266.86193848, -266.86169434, -266.86407471, -266.86123657,\n", + " -266.85812378, -266.85818481],\n", + " [ 685.69683838, 685.69873047, 685.6986084 , 685.69775391,\n", + " 685.7041626 , 685.69592285],\n", + " [ 11. , 11. , 11. , 11. ,\n", + " 11. , 11. ]])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (time: 6, axis: 4)>\n", + "array([[ 44.16278839, -276.86193848, 675.69683838, 1. ],\n", + " [ 44.16666412, -276.86169434, 675.69873047, 1. ],\n", + " [ 44.16487122, -276.86407471, 675.6986084 , 1. ],\n", + " [ 44.16558075, -276.86123657, 675.69775391, 1. ],\n", + " [ 44.17311096, -276.85812378, 675.7041626 , 1. ],\n", + " [ 44.18517685, -276.85818481, 675.69592285, 1. ]])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'\n", + " * time (time) float64 0.0 0.01 0.02 0.03 0.04 0.05\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm\n", + " description: Skin marker positions recorded in Montreal.\n", + " participant_id: 12" + ], + "text/plain": [ + "\n", + "array([[ 44.16278839, -276.86193848, 675.69683838, 1. ],\n", + " [ 44.16666412, -276.86169434, 675.69873047, 1. ],\n", + " [ 44.16487122, -276.86407471, 675.6986084 , 1. ],\n", + " [ 44.16558075, -276.86123657, 675.69775391, 1. ],\n", + " [ 44.17311096, -276.85812378, 675.7041626 , 1. ],\n", + " [ 44.18517685, -276.85818481, 675.69592285, 1. ]])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' ()>\n", + "array(111.00187318)\n", + "Coordinates:\n", + " channel <U14 'gauche_ext'" + ], + "text/plain": [ + "\n", + "array(111.00187318)\n", + "Coordinates:\n", + " channel <xarray.DataArray 'markers' (axis: 4)>\n", + "array([ 44.16969872, -276.86087545, 675.69866943, 1. ])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'" + ], + "text/plain": [ + "\n", + "array([ 44.16969872, -276.86087545, 675.69866943, 1. ])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4)>\n", + "array([ 44.16969872, -276.86087545, 675.69866943, 1. ])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'" + ], + "text/plain": [ + "\n", + "array([ 44.16969872, -276.86087545, 675.69866943, 1. ])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (time: 6)>\n", + "array([44.16278839, 44.16666412, 44.16487122, 44.16558075, 44.17311096,\n", + " 44.18517685])\n", + "Coordinates:\n", + " axis <U4 'x'\n", + " channel <U14 'gauche_ext'\n", + " * time (time) float64 0.0 0.01 0.02 0.03 0.04 0.05\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm\n", + " description: Skin marker positions recorded in Montreal.\n", + " participant_id: 12" + ], + "text/plain": [ + "\n", + "array([44.16278839, 44.16666412, 44.16487122, 44.16558075, 44.17311096,\n", + " 44.18517685])\n", + "Coordinates:\n", + " axis <xarray.DataArray 'markers' (axis: 4)>\n", + "array([ 44.16278839, -276.86193848, 675.69683838, 1. ])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'\n", + " time float64 0.0\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 579\n", + " rate: 100.0\n", + " units: mm\n", + " description: Skin marker positions recorded in Montreal.\n", + " participant_id: 12" + ], + "text/plain": [ + "\n", + "array([ 44.16278839, -276.86193848, 675.69683838, 1. ])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4, time: 6)>\n", + "array([[ 88.32557678, 88.32945251, 88.32765961, 88.32836914,\n", + " 88.33589935, 88.34796524],\n", + " [-232.69915009, -232.69527435, -232.69706726, -232.69635773,\n", + " -232.68882751, -232.67676163],\n", + " [ 719.85962677, 719.8635025 , 719.86170959, 719.86241913,\n", + " 719.86994934, 719.88201523],\n", + " [ 45.16278839, 45.16666412, 45.16487122, 45.16558075,\n", + " 45.17311096, 45.18517685]])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'\n", + " * time (time) float64 0.0 0.01 0.02 0.03 0.04 0.05" + ], + "text/plain": [ + "\n", + "array([[ 88.32557678, 88.32945251, 88.32765961, 88.32836914,\n", + " 88.33589935, 88.34796524],\n", + " [-232.69915009, -232.69527435, -232.69706726, -232.69635773,\n", + " -232.68882751, -232.67676163],\n", + " [ 719.85962677, 719.8635025 , 719.86170959, 719.86241913,\n", + " 719.86994934, 719.88201523],\n", + " [ 45.16278839, 45.16666412, 45.16487122, 45.16558075,\n", + " 45.17311096, 45.18517685]])\n", + "Coordinates:\n", + " * axis (axis) <xarray.DataArray 'markers' (axis: 4, time: 6)>\n", + "array([[0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0.]])\n", + "Coordinates:\n", + " * axis (axis) <U4 'x' 'y' 'z' 'ones'\n", + " channel <U14 'gauche_ext'\n", + " * time (time) float64 0.0 0.01 0.02 0.03 0.04 0.05" + ], + "text/plain": [ + "\n", + "array([[0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0.]])\n", + "Coordinates:\n", + " * axis (axis) > ValueError: operands could not be broadcast together with shapes (4,6) (6,4) \n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "!!! note\n", + " xarray supports powerful shortcuts for computation.\n", + " For more, see the [xarray documentation](http://xarray.pydata.org/en/stable/computation.html#comput)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Application: exploring missing values\n", + "\n", + "When we visualize some of our markers, we can realize that there are some missing values." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "markers.sel(axis=\"x\", channel=\"SCAP_CP\").plot.line(x=\"time\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's investigate those missing values" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 305 missing values\n" + ] + } + ], + "source": [ + "markers_null_values = markers.sel(axis=\"x\").isnull()\n", + "print(f\"There are {markers_null_values.sum().values} missing values\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What are the 5 markers with the most missing values?" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "markers_null_values.sum(dim=\"time\").to_series().nlargest(5).plot.barh();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cumulative number of missing values can tell us when marker occlusions occur." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "markers_null_values.sum(\"channel\").cumsum(\"time\").plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we know more about the missing values, we can use xarray for filling missing values via 1D interpolation." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "markers_without_null = markers.interpolate_na(dim=\"time\", method=\"cubic\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's visualize our interpolated markers." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "markers.sel(axis=\"x\", channel=\"SCAP_CP\").plot.line(x=\"time\")\n", + "(\n", + " markers_without_null.where(markers.isnull())\n", + " .sel(axis=\"x\", channel=\"SCAP_CP\")\n", + " .plot.line(x=\"time\", label=\"interpolated\", color=\"r\")\n", + ")\n", + "plt.legend();" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "(\n", + " markers_without_null.drop_sel(axis=\"ones\") # drop the axis with only ones\n", + " .isel(channel=slice(10, 16)) # select some markers\n", + " .plot.line(x=\"time\", col=\"channel\", hue=\"axis\", col_wrap=3) # plot the data\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Application: electromyographic pipeline\n", + "\n", + "Pyomeca implements specialized functionalities commonly used in biomechanics.\n", + "\n", + "As an example, let's process the electromyographic data contained in our `c3d` file." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "from pyomeca import Analogs\n", + "\n", + "muscles = [\n", + " \"Delt_ant\",\n", + " \"Delt_med\",\n", + " \"Delt_post\",\n", + " \"Supra\",\n", + " \"Infra\",\n", + " \"Subscap\",\n", + "]\n", + "emg = Analogs.from_c3d(data_path, suffix_delimiter=\".\", usecols=muscles)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "emg.plot(x=\"time\", col=\"channel\", col_wrap=3);" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "emg_processed = (\n", + " emg.meca.band_pass(order=2, cutoff=[10, 425])\n", + " .meca.center()\n", + " .meca.abs()\n", + " .meca.low_pass(order=4, cutoff=5, freq=emg.rate)\n", + " .meca.normalize()\n", + ")\n", + "\n", + "emg_processed.plot(x=\"time\", col=\"channel\", col_wrap=3);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By updating the metadata (`attrs` dictionary), we can update the name and units on our plots." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "emg_processed.name = \"EMG\"\n", + "emg_processed.attrs[\"units\"] = \"%\"\n", + "emg_processed.time.attrs[\"units\"] = \"seconds\"\n", + "\n", + "emg_processed.plot(x=\"time\", col=\"channel\", col_wrap=3);" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(ncols=2, figsize=(10, 4))\n", + "\n", + "emg_processed.mean(\"channel\").plot(ax=axes[0])\n", + "axes[0].set_title(\"Mean EMG activation\")\n", + "\n", + "emg_processed.plot.hist(ax=axes[1], bins=50)\n", + "axes[1].set_title(\"EMG activation distribution\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By converting the data array to a pandas dataframe, we can further extend the possibilities:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "emg_dataframe = emg_processed.meca.to_wide_dataframe()\n", + "emg_dataframe.plot.box(showfliers=False);" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
channel Delt_ant Delt_med Delt_post Infra Subscap Supra
channel
Delt_ant1.00.780.380.740.60.6
Delt_med0.781.00.770.740.760.9
Delt_post0.380.771.00.620.670.84
Infra0.740.740.621.00.610.75
Subscap0.60.760.670.611.00.78
Supra0.60.90.840.750.781.0
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "emg_dataframe.corr().style.background_gradient().set_precision(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "!!! note\n", + " For more details, see the [data processing](https://pyomeca.github.io/data-processing/) section of the documentation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Datasets\n", + "\n", + "Datasets are a useful xarray feature to store multiple data arrays with common dimensions" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "import xarray as xr\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
<xarray.Dataset>\n",
+       "Dimensions:  (channel: 6, time: 11600)\n",
+       "Coordinates:\n",
+       "  * channel  (channel) <U9 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'\n",
+       "  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n",
+       "Data variables:\n",
+       "    trial 1  (channel, time) float64 -2.609e-05 -2.544e-05 ... 3.04e-05\n",
+       "    trial 2  (channel, time) float64 -5.21e-06 -5.081e-06 ... 6.071e-06\n",
+       "    trial 3  (channel, time) float64 -1.355e-05 -1.321e-05 ... 1.578e-05
" + ], + "text/plain": [ + "\n", + "Dimensions: (channel: 6, time: 11600)\n", + "Coordinates:\n", + " * channel (channel) <xarray.DataArray 'trial 1' (channel: 6, time: 11600)>\n", + "array([[-2.60891229e-05, -2.54411752e-05, -2.45576939e-05, ...,\n", + " -1.93931046e-05, -1.97550762e-05, -1.91258678e-05],\n", + " [-4.02107289e-05, -6.80835801e-05, -8.64052563e-05, ...,\n", + " 4.98310801e-05, 4.22991216e-05, 3.81295613e-05],\n", + " [-1.36110339e-05, -1.32793148e-05, -1.27393068e-05, ...,\n", + " -2.54613460e-05, -2.30687110e-05, -1.89858001e-05],\n", + " [ 2.97530321e-04, 1.55170274e-04, 3.52776406e-05, ...,\n", + " -2.58133630e-04, -4.46292252e-04, -5.75785409e-04],\n", + " [ 2.23239495e-05, 2.26500360e-05, 2.28447316e-05, ...,\n", + " 8.95366975e-06, -3.41701161e-06, 1.05170475e-05],\n", + " [ 2.87022194e-05, 2.90283060e-05, 2.90723892e-05, ...,\n", + " 2.92962086e-05, 2.90890512e-05, 3.03986035e-05]])\n", + "Coordinates:\n", + " * channel (channel) <U9 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'\n", + " * time (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 11580\n", + " rate: 2000.0\n", + " units: V" + ], + "text/plain": [ + "\n", + "array([[-2.60891229e-05, -2.54411752e-05, -2.45576939e-05, ...,\n", + " -1.93931046e-05, -1.97550762e-05, -1.91258678e-05],\n", + " [-4.02107289e-05, -6.80835801e-05, -8.64052563e-05, ...,\n", + " 4.98310801e-05, 4.22991216e-05, 3.81295613e-05],\n", + " [-1.36110339e-05, -1.32793148e-05, -1.27393068e-05, ...,\n", + " -2.54613460e-05, -2.30687110e-05, -1.89858001e-05],\n", + " [ 2.97530321e-04, 1.55170274e-04, 3.52776406e-05, ...,\n", + " -2.58133630e-04, -4.46292252e-04, -5.75785409e-04],\n", + " [ 2.23239495e-05, 2.26500360e-05, 2.28447316e-05, ...,\n", + " 8.95366975e-06, -3.41701161e-06, 1.05170475e-05],\n", + " [ 2.87022194e-05, 2.90283060e-05, 2.90723892e-05, ...,\n", + " 2.92962086e-05, 2.90890512e-05, 3.03986035e-05]])\n", + "Coordinates:\n", + " * channel (channel) <xarray.Dataset>\n", + "Dimensions: (time: 11600)\n", + "Coordinates:\n", + " channel <U9 'Infra'\n", + " * time (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n", + "Data variables:\n", + " trial 1 (time) float64 100.0 100.0 100.0 100.0 ... 100.0 100.0 100.0 100.0\n", + " trial 2 (time) float64 100.0 100.0 100.0 100.0 ... 100.0 100.0 100.0 100.0\n", + " trial 3 (time) float64 100.0 100.0 100.0 100.0 ... 100.0 100.0 100.0 100.0" + ], + "text/plain": [ + "\n", + "Dimensions: (time: 11600)\n", + "Coordinates:\n", + " channel <xarray.DataArray 'analogs' (channel: 6, time: 11600)>\n", + "array([[-2.608912e-05, -2.544118e-05, -2.455769e-05, ..., -1.939310e-05,\n", + " -1.975508e-05, -1.912587e-05],\n", + " [-4.021073e-05, -6.808358e-05, -8.640526e-05, ..., 4.983108e-05,\n", + " 4.229912e-05, 3.812956e-05],\n", + " [-1.361103e-05, -1.327931e-05, -1.273931e-05, ..., -2.546135e-05,\n", + " -2.306871e-05, -1.898580e-05],\n", + " [ 2.975303e-04, 1.551703e-04, 3.527764e-05, ..., -2.581336e-04,\n", + " -4.462923e-04, -5.757854e-04],\n", + " [ 2.232395e-05, 2.265004e-05, 2.284473e-05, ..., 8.953670e-06,\n", + " -3.417012e-06, 1.051705e-05],\n", + " [ 2.870222e-05, 2.902831e-05, 2.907239e-05, ..., 2.929621e-05,\n", + " 2.908905e-05, 3.039860e-05]])\n", + "Coordinates:\n", + " * time (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n", + " * channel (channel) object 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 11580\n", + " rate: 2000.0\n", + " units: V" + ], + "text/plain": [ + "\n", + "array([[-2.608912e-05, -2.544118e-05, -2.455769e-05, ..., -1.939310e-05,\n", + " -1.975508e-05, -1.912587e-05],\n", + " [-4.021073e-05, -6.808358e-05, -8.640526e-05, ..., 4.983108e-05,\n", + " 4.229912e-05, 3.812956e-05],\n", + " [-1.361103e-05, -1.327931e-05, -1.273931e-05, ..., -2.546135e-05,\n", + " -2.306871e-05, -1.898580e-05],\n", + " [ 2.975303e-04, 1.551703e-04, 3.527764e-05, ..., -2.581336e-04,\n", + " -4.462923e-04, -5.757854e-04],\n", + " [ 2.232395e-05, 2.265004e-05, 2.284473e-05, ..., 8.953670e-06,\n", + " -3.417012e-06, 1.051705e-05],\n", + " [ 2.870222e-05, 2.902831e-05, 2.907239e-05, ..., 2.929621e-05,\n", + " 2.908905e-05, 3.039860e-05]])\n", + "Coordinates:\n", + " * time (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n", + " * channel (channel) object 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'\n", + "Attributes:\n", + " first_frame: 0\n", + " last_frame: 11580\n", + " rate: 2000.0\n", + " units: V" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xr.open_dataarray(\"emg.nc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "trials.to_netcdf(\"trials.nc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
<xarray.Dataset>\n",
+       "Dimensions:  (channel: 6, time: 11600)\n",
+       "Coordinates:\n",
+       "  * time     (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n",
+       "  * channel  (channel) object 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'\n",
+       "Data variables:\n",
+       "    trial 1  (channel, time) float64 ...\n",
+       "    trial 2  (channel, time) float64 ...\n",
+       "    trial 3  (channel, time) float64 ...
" + ], + "text/plain": [ + "\n", + "Dimensions: (channel: 6, time: 11600)\n", + "Coordinates:\n", + " * time (time) float64 0.0 0.0005 0.001 0.0015 ... 5.798 5.798 5.799 5.8\n", + " * channel (channel) object 'Delt_ant' 'Delt_med' ... 'Infra' 'Subscap'\n", + "Data variables:\n", + " trial 1 (channel, time) float64 ...\n", + " trial 2 (channel, time) float64 ...\n", + " trial 3 (channel, time) float64 ..." + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xr.open_dataset(\"trials.nc\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pyomeca implements function to read various file format commonly used in biomechanics such as `c3d`, `csv`, `xlsx`, `sto`, `trc` and `mot`.\n", + "\n", + "Users can also write Matlab and csv files." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "emg.meca.to_matlab(\"emg.mat\")" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "emg.meca.to_csv(\"emg.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "# ignore_cell\n", + "from pathlib import Path\n", + "\n", + "files = [\"emg.mat\", \"emg.csv\", \"emg.nc\", \"trials.nc\"]\n", + "\n", + "for file in files:\n", + " Path(file).unlink()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:pyomeca]", + "language": "python", + "name": "conda-env-pyomeca-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/readme-example.ipynb b/notebooks/readme-example.ipynb new file mode 100644 index 0000000..16b5725 --- /dev/null +++ b/notebooks/readme-example.ipynb @@ -0,0 +1,23961 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# ignore_cell\n", + "# You can safely ignore this cell\n", + "\n", + "# add pyomeca to local path (to make it usable in binder server)\n", + "import sys\n", + "\n", + "sys.path.append(\"..\")\n", + "\n", + "# Plot style\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import set_matplotlib_formats\n", + "\n", + "set_matplotlib_formats(\"svg\")\n", + "mpl.rcParams[\"axes.spines.right\"] = False\n", + "mpl.rcParams[\"axes.spines.top\"] = False\n", + "plt.style.use(\"seaborn-ticks\")\n", + "%matplotlib inline\n", + "# %load_ext lab_black" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Application: electromyographic pipeline\n", + "\n", + "Pyomeca implements specialized functionalities commonly used in biomechanics. As an example, let's process the electromyographic data contained in this [`c3d file`](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers_analogs.c3d).\n", + "\n", + "You can follow along without installing anything by using our binder server: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyomeca/pyomeca/master?filepath=notebooks)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from pyomeca import Analogs\n", + "\n", + "data_path = \"../tests/data/markers_analogs.c3d\"\n", + "muscles = [\n", + " \"Delt_ant\",\n", + " \"Delt_med\",\n", + " \"Delt_post\",\n", + " \"Supra\",\n", + " \"Infra\",\n", + " \"Subscap\",\n", + "]\n", + "emg = Analogs.from_c3d(data_path, suffix_delimiter=\".\", usecols=muscles)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "emg.plot(x=\"time\", col=\"channel\", col_wrap=3);" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "emg_processed = (\n", + " emg.meca.band_pass(order=2, cutoff=[10, 425])\n", + " .meca.center()\n", + " .meca.abs()\n", + " .meca.low_pass(order=4, cutoff=5, freq=emg.rate)\n", + " .meca.normalize()\n", + ")\n", + "\n", + "emg_processed.plot(x=\"time\", col=\"channel\", col_wrap=3);" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'EMG activation distribution')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "fig, axes = plt.subplots(ncols=2, figsize=(10, 4))\n", + "\n", + "emg_processed.mean(\"channel\").plot(ax=axes[0])\n", + "axes[0].set_title(\"Mean EMG activation\")\n", + "\n", + "emg_processed.plot.hist(ax=axes[1], bins=50)\n", + "axes[1].set_title(\"EMG activation distribution\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:pyomeca]", + "language": "python", + "name": "conda-env-pyomeca-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/pyomeca/__init__.py b/pyomeca/__init__.py index 2a83ee4..afc13e1 100644 --- a/pyomeca/__init__.py +++ b/pyomeca/__init__.py @@ -1,5 +1,5 @@ -from .frame_dependent import * -from .analogs import * -from .generalized_coordinates import * -from .markers import * -from .rototrans import * +from .analogs import Analogs +from .angles import Angles +from .dataarray_accessor import DataArrayAccessor +from .markers import Markers +from .rototrans import Rototrans diff --git a/pyomeca/analogs.py b/pyomeca/analogs.py index 6654e36..4acf3d2 100644 --- a/pyomeca/analogs.py +++ b/pyomeca/analogs.py @@ -1,371 +1,508 @@ from pathlib import Path +from typing import List, Optional, Tuple, Union -import matplotlib.pyplot as plt import numpy as np import pandas as pd +import xarray as xr -from pyomeca import FrameDependentNpArray +from pyomeca.io import read, utils -class Analogs3d(FrameDependentNpArray): - def __new__(cls, data=np.ndarray((1, 0, 0)), names=list(), *args, **kwargs): +class Analogs: + def __new__( + cls, + data: Optional[Union[np.array, np.ndarray, xr.DataArray]] = None, + channels: Optional[list] = None, + time: Optional[Union[np.array, list, pd.Series]] = None, + **kwargs, + ) -> xr.DataArray: """ - Parameters - ---------- - data : np.ndarray - 1xNxF matrix of analogs data - names : list of string - name of the analogs that correspond to second dimension of the matrix - """ - if data.ndim == 2: - data = Analogs3d.from_2d(data) - - if data.ndim == 3: - s = data.shape - if s[0] != 1: - raise IndexError( - "Analogs3d must have a length of 1 on the first dimension" - ) - analog = data - else: - raise TypeError("Data must be 2d or 3d matrix") + Analogs DataArray with `channel` and `time` dimensions + used for generic signals such as EMGs, force signals or any other analog signals. + ![analogs](/images/objects/analogs.svg) - return super(Analogs3d, cls).__new__(cls, array=analog, *args, **kwargs) + Arguments: + data: Array to be passed to xarray.DataArray + channels: Channel names + time: Time vector in seconds associated with the `data` parameter + kwargs: Keyword argument(s) to be passed to xarray.DataArray - def __array_finalize__(self, obj): - super().__array_finalize__(obj) - # Allow slicing - if obj is None or not isinstance(obj, Analogs3d): - return + Returns: + Analogs `xarray.DataArray` with the specified data and coordinates - # --- Get metadata methods + !!! example + To instantiate an `Analogs` with 4 channels and 100 frames filled with some random data: - def get_num_analogs(self): - """ - Returns - ------- - The number of analogs - """ - return self.shape[1] + ```python + import numpy as np + from pyomeca import Analogs + + n_channels = 4 + n_frames = 100 + data = np.random.random(size=(n_channels, n_frames)) + analogs = Analogs(data) + ``` - def get_2d_labels(self): + You can add the channel names: + + ```python + names = ["A", "B", "C", "D"] + analogs = Analogs(data, channels=names) + ``` + + And an associate time vector: + + ```python + rate = 100 # Hz + time = np.arange(start=0, stop=n_frames / rate, step=1 / rate) + analogs = Analogs(data, channels=names, time=time) + ``` + + !!! note + Calling `Analogs()` generate an empty array. """ - Takes a Analogs style labels and returns 2d style labels - Returns - ------- - 2d style labels + coords = {} + if data is None: + data = np.ndarray((0, 0)) + if channels is not None: + coords["channel"] = channels + if time is not None: + coords["time"] = time + return xr.DataArray( + data=data, + dims=("channel", "time"), + coords=coords, + name="analogs", + **kwargs, + ) + + @classmethod + def from_random_data( + cls, distribution: str = "normal", size: tuple = (10, 100), **kwargs + ) -> xr.DataArray: """ - return self.get_labels + Create random data from a specified distribution (normal by default) using random walk. - # --- Fileio methods (from_*) + Arguments: + distribution: Distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions) + size: Shape of the desired array + kwargs: Keyword argument(s) to be passed to numpy.random.`distribution` - @staticmethod - def from_2d(m): - """ - Takes a tabular matrix and returns a Vectors3d - Parameters - ---------- - m : np.array - A CSV tabular matrix (Fx3*N) - Returns - ------- - Vectors3d of data set + Returns: + Random Analogs `xarray.DataArray` sampled from a given distribution + + !!! example + To instantiate an `Analogs` with some random data sampled from a normal distribution: + + ```python + from pyomeca import Analogs + + n_channels = 10 + n_frames = 100 + size = n_channels, n_frames + analogs = Analogs.from_random_data(size=size) + ``` + + You can choose any distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions): + + ```python + analogs = Analogs.from_random_data(distribution="uniform", size=size, low=1, high=10) + ``` """ - s = m.shape - return Analogs3d(np.reshape(m.T, (1, s[1], s[0]), "F")) + return Analogs(getattr(np.random, distribution)(size=size, **kwargs).cumsum(-1)) @classmethod - def from_mot(cls, filename): - mot = cls.from_csv( - filename, header=8, first_column=1, time_column=0, delimiter="\t" + def from_csv( + cls, + filename: Union[str, Path], + usecols: Optional[List[Union[str, int]]] = None, + header: Optional[int] = None, + first_row: int = 0, + first_column: Optional[Union[str, int]] = None, + time_column: Optional[Union[str, int]] = None, + trailing_columns: Optional[Union[str, int]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + skip_rows: Optional[List[int]] = None, + pandas_kwargs: Optional[dict] = None, + attrs: Optional[dict] = None, + ) -> xr.DataArray: + """ + Analogs DataArray from a csv file. + + Arguments: + filename: Any valid string path + usecols: All elements must either be positional or strings that correspond to column names. + For example, a valid list-like usecols parameter would be [0, 1, 2] or ['foo', 'bar', 'baz'] + header: Row of the header (0-indexed) + first_row: First row of the data (0-indexed) + first_column: First column of the data (0-indexed) + time_column: Location of the time column. If None, indices are associated + trailing_columns: If for some reason the csv reads extra columns, how many should be ignored + prefix_delimiter: Delimiter that split each column name by its prefix (we keep only the column name) + suffix_delimiter: Delimiter that split each column name by its suffix (we keep only the column name) + skip_rows: Line numbers to skip (0-indexed) + pandas_kwargs: Keyword arguments to be passed to `pandas.read_csv` + attrs: attrs to be passed to `xr.DataArray`. If attrs['rate'] is provided, compute the time accordingly + + Returns: + Analogs `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this csv file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/analogs.csv), + type: + + ```python + from pyomeca import Analogs + + data_path = "./tests/data/analogs.csv" + analogs = Analogs.from_csv(data_path, header=3, first_row=5, first_column=2) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: + + ```python + channels = ["IM EMG1", "IM EMG2", "IM EMG3"] + analogs = Analogs.from_csv( + data_path, header=3, first_row=5, first_column=2, usecols=channels + ) + ``` + + Or by position: + + ```python + channels = [5, 6, 7] + analogs = Analogs.from_csv( + data_path, header=3, first_row=5, first_column=2, usecols=channels + ) + ``` + + Sometimes the channel name is delimited by a suffix or prefix. + To access the prefix, you can specify `prefix_delimiter` and `suffix_delimiter` for the suffix. + For example, if the name is `"IM EMG1"` and you specify `suffix_delimiter=" "`, you will select "IM". + Similarly, if you specify `prefix_delimiter=" ": + + ```python + channels = ["EMG1", "EMG2", "EMG3"] + analogs = Analogs.from_csv( + data_path, + header=3, + first_row=5, + first_column=2, + usecols=channels, + suffix_delimiter=" ", + ) + ``` + + It is also possible to specify a column containing the time vector: + + ```python + analogs = Analogs.from_csv( + data_path, header=3, first_row=5, first_column=1, time_column=0 + ) + ``` + """ + return read.read_csv_or_excel( + cls, + "csv", + filename, + usecols, + header, + first_row, + first_column, + time_column, + trailing_columns, + prefix_delimiter, + suffix_delimiter, + skip_rows, + pandas_kwargs, + attrs, ) - mot.get_rate = (1 / (mot.get_time_frames[1] - mot.get_time_frames[0])).round() - return mot @classmethod - def from_sto(cls, filename, endheader_range=20, na_values=None): - # detect where 'endheader' is - meta = pd.read_csv( - filename, usecols=[0], nrows=endheader_range, delimiter="\t" - ).values.ravel() - end_header = np.argwhere((meta == "endheader"))[0][0] + 2 - if end_header: - sto = cls.from_csv( - filename, - header=end_header, - first_column=1, - time_column=0, - delimiter="\t", - na_values=na_values, + def from_excel( + cls, + filename: Union[str, Path], + sheet_name: Union[str, int] = 0, + usecols: Optional[List[Union[str, int]]] = None, + header: Optional[int] = None, + first_row: int = 0, + first_column: Optional[Union[str, int]] = None, + time_column: Optional[Union[str, int]] = None, + trailing_columns: Optional[Union[str, int]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + skip_rows: Optional[List[int]] = None, + pandas_kwargs: Optional[dict] = None, + attrs: Optional[dict] = None, + ) -> xr.DataArray: + """ + Analogs DataArray from a excel file. + + Arguments: + filename: Any valid string path + sheet_name: Strings are used for sheet names. Integers are used in zero-indexed sheet positions + usecols: All elements must either be positional or strings that correspond to column names. + For example, a valid list-like usecols parameter would be [0, 1, 2] or ['foo', 'bar', 'baz'] + header: Row of the header (0-indexed) + first_row: First row of the data (0-indexed) + first_column: First column of the data (0-indexed) + time_column: Location of the time column. If None, indices are associated + trailing_columns: If for some reason the csv reads extra columns, how many should be ignored + prefix_delimiter: Delimiter that split each column name by its prefix (we keep only the column name) + suffix_delimiter: Delimiter that split each column name by its suffix (we keep only the column name) + skip_rows: Line numbers to skip (0-indexed) + pandas_kwargs: Keyword arguments to be passed to `pandas.read_excel` + attrs: attrs to be passed to `xr.DataArray`. If attrs['rate'] is provided, compute the time accordingly + + Returns: + Analogs `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this excel file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/analogs.xlsx), + type: + + ```python + from pyomeca import Analogs + + data_path = "./tests/data/analogs.xlsx" + analogs = Analogs.from_excel(data_path, header=3, first_row=5, first_column=2) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: + + ```python + channels = ["A"] + analogs = Analogs.from_excel( + data_path, header=3, first_row=5, first_column=2, usecols=channels ) - sto.get_rate = ( - 1 / (sto.get_time_frames[1] - sto.get_time_frames[0]) - ).round() - else: - raise ValueError( - f"""endheader" not detected in the first {endheader_range} rows. - Try increasing the `endheader_range` parameter'""" + ``` + + Or by position: + + ```python + channels = [1] + analogs = Analogs.from_excel( + data_path, header=3, first_row=5, first_column=2, usecols=channels ) - return sto + ``` - # --- Fileio methods (to_*) + It is also possible to specify a column containing the time vector: + + ```python + analogs = Analogs.from_excel( + data_path, header=3, first_row=5, first_column=1, time_column=0 + ) + ``` - def to_2d(self): - """ - Takes a Analogs3d style matrix and returns a tabular matrix - Returns - ------- - Tabular matrix """ - return np.squeeze(self.T, axis=2) + return read.read_csv_or_excel( + cls, + "excel", + filename, + usecols, + header, + first_row, + first_column, + time_column, + trailing_columns, + prefix_delimiter, + suffix_delimiter, + skip_rows, + pandas_kwargs, + attrs, + sheet_name, + ) - @staticmethod - def _parse_c3d(c3d, prefix): + @classmethod + def from_sto( + cls, filename: Union[str, Path], end_header: Optional[bool] = None, **kwargs + ) -> xr.DataArray: """ - Implementation on how to read c3d header and parameter for analogs - Parameters - ---------- - c3d : ezc3d + Analogs DataArray from a sto file. - prefix : str, optional - Participant's prefix + Arguments: + filename: Any valid string path + end_header: Index where `endheader` appears (0 indexed). + If not provided, the index is automatically determined + kwargs: Keyword arguments to be passed to `from_csv` - Returns - ------- - metadata, channel_names, data - """ - channel_names = [ - i.split(prefix)[-1] - for i in c3d.parameters() - .group("ANALOG") - .parameter("LABELS") - .valuesAsString() - ] - metadata = { - "get_num_analogs": c3d.header().nbAnalogs(), - "get_num_frames": c3d.header().nbAnalogsMeasurement(), - "get_first_frame": c3d.header().firstFrame() - * c3d.header().nbAnalogByFrame(), - "get_last_frame": c3d.header().lastFrame() * c3d.header().nbAnalogByFrame(), - "get_time_frames": None, - "get_rate": c3d.header().frameRate() * c3d.header().nbAnalogByFrame(), - "get_unit": [], - } - data = c3d.get_analogs() - - return data, channel_names, metadata - - def rectify(self): + Returns: + Analogs `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this sto file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/inverse_dyn.sto), + type: + + ```python + from pyomeca import Analogs + + data_path = "./tests/data/inverse_dyn.sto" + analogs = Analogs.from_sto(data_path) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: + + ```python + channels = ["shoulder_plane_moment", "shoulder_ele_moment"] + analogs = Analogs.from_sto(data_path, usecols=channels) + ``` + + Or by position: + + ```python + channels = [3, 4] + analogs = Analogs.from_sto(data_path, usecols=channels) + ``` """ - Rectify a signal (i.e., get absolute values) + return read.read_sto_or_mot(cls, filename, end_header, **kwargs) - Returns - ------- - FrameDependentNpArray + @classmethod + def from_mot( + cls, filename: Union[str, Path], end_header: Optional[bool] = None, **kwargs + ) -> xr.DataArray: """ - return self.abs() - - -class MVC: - """ - Return the Maximal Voluntary Contraction (MVA) array. - MVA is computed as follow: - 1. read the files - 2. process trial (band-pass, center, rectify, low-pass). You can modify the parameters of these steps in - self.params - 3. detect onset - 4. remove data that are more than `outlier` standard deviations from the average of the onset - 5. concatenate all trials for a given muscle - 6. get mean of the highest sorted_values activation during `time` seconds - - Parameters - ---------- - directories : list - List of directories containing the trials to be processed - channels : list - List (or list of lists) of string associated with each channel - plot_trials : bool - If the plot of each trial must be displayed - plot_mva : bool - If the plot of each mva must be displayed - outlier : int - Multiple of standard deviation from which data is considered outlier - band_pass_cutoff : list - Band-pass cut-off frequencies - low_pass_cutoff : int - Low-pass cut-off frequencies - order : int - Order of the filter - """ - - def __init__( - self, - directories, - channels, - plot_trials=False, - plot_mva=False, - outlier=3, - band_pass_cutoff=None, - low_pass_cutoff=None, - order=4, - ): - self.trials_path = [] - for idir in directories: - idir = Path(idir) - if not idir.is_dir(): - raise ValueError(f"{str(idir)} does not exist.") - for ifile in idir.glob("*.c3d"): - self.trials_path.append(ifile) - # make a nested list if not already nested - if not any(isinstance(i, list) for i in channels): - channels = [channels] - self.channels = channels - self.plot_trials = plot_trials - self.plot_mva = plot_mva - - self.band_pass_cutoff = band_pass_cutoff if band_pass_cutoff else [10, 425] - self.low_pass_cutoff = low_pass_cutoff if low_pass_cutoff else 5 - self.order = order - self.outlier = outlier - - self.trials = self.read_files() - self.concatenated = self.process_trials() - - def read_files(self): - """Read c3d files and append them to a list""" - trials = [] - for itrial in self.trials_path: - for iassign in self.channels: - # get index where assignment are empty - nan_idx = [i for i, v in enumerate(iassign) if not v] - if nan_idx: - iassign_without_nans = [i for i in iassign if i] - else: - iassign_without_nans = iassign - - try: - emg = Analogs3d.from_c3d( - f"{itrial}", prefix=":", names=iassign_without_nans - ) - if nan_idx: - # if there is any empty assignment, fill the dimension with nan - emg.get_nan_idx = np.array(nan_idx) - for i in nan_idx: - emg = np.insert(emg, i, np.nan, axis=1) - # check if nan dimension are correctly inserted - n = np.isnan(emg).sum(axis=2).ravel() - if not np.array_equal(n.argsort()[-len(nan_idx) :], nan_idx): - raise ValueError("NaN dimensions misplaced") - print(f"\t{itrial.stem} (NaNs: {nan_idx})") - else: - print(f"\t{itrial.stem}") - - # check if dimensions are ok - if not emg.shape[1] == len(iassign): - raise ValueError("Wrong dimensions") - break - except IndexError: - emg = [] - - if np.any(emg): - trials.append(emg) - else: - raise ValueError( - f"no assignments were found for the trial {itrial.stem}" - ) + Analogs DataArray from a mot file. - return trials + Arguments: + filename: Any valid string path + end_header: Index where `endheader` appears (0 indexed). If not provided, the index is automatically determined. + kwargs: Keyword arguments to be passed to `from_csv` - def process_trials(self): - """Process trials from a list and concatenate them in a single dict""" - print("Processing trials...") - concatenated = { - imuscle: np.array([]) for imuscle in range(self.trials[0].shape[1]) - } + Returns: + Analogs `xarray.DataArray` with the specified data and coordinates - for i, itrial in enumerate(self.trials): - # emg processing - itrial = ( - itrial.band_pass( - freq=itrial.get_rate, order=self.order, cutoff=self.band_pass_cutoff - ) - .center() - .rectify() - .low_pass( - freq=itrial.get_rate, order=self.order, cutoff=self.low_pass_cutoff - ) - ) + !!! example + To read [this mot file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/inverse_kin.mot), + type: - for imuscle in range(itrial.shape[1]): - if self.channels[0][imuscle] == "": - concatenated[imuscle] = np.append(concatenated[imuscle], np.nan) - else: - x = itrial[0, imuscle, :] - # onset detection - idx = x.detect_onset( - threshold=np.nanmean(x[..., : int(itrial.get_rate)]), - above=int(itrial.get_rate) / 2, - below=3, - threshold2=np.nanmean(x[..., : int(itrial.get_rate)]) * 2, - above2=5, - ) + ```python + from pyomeca import Analogs - # outliers detection - x_without_outliers = x.detect_outliers( - onset_idx=idx, threshold=self.outlier - ) + data_path = "./tests/data/inverse_kin.mot" + analogs = Analogs.from_mot(data_path) + ``` - # append the current trial to a dictionary concatenating all trials for each of the muscles - concatenated[imuscle] = np.append( - concatenated[imuscle], np.ma.compressed(x_without_outliers) - ) + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: - if self.plot_trials: - plt.plot(x_without_outliers, "k-") - if x_without_outliers.mask.any(): - plt.plot( - np.ma.masked_array(x, ~x_without_outliers.mask), - "r-", - label="outlier", - ) - plt.legend() - plt.title( - f"{self.channels[0][imuscle]} | {self.trials_path[i].stem}" - ) - plt.show() - return concatenated + ```python + channels = ["elbow_flexion", "pro_sup"] + analogs = Analogs.from_mot(data_path, usecols=channels) + ``` + + Or by position: - def get_mva(self, time=2): + ```python + channels = [3, 4] + analogs = Analogs.from_mot(data_path, usecols=channels) + ``` """ - Return the Maximal Voluntary Contraction (MVA) array. - MVA is computed as follow: - for each muscle: - 1. sort the vector of all the concatenated trials - 2. get mean of the highest sorted_values activation during `time` seconds - - Parameters - ---------- - time : int - Time during which the average is calculated - - Returns - ------- - numpy.ndarray + return read.read_sto_or_mot(cls, filename, end_header, **kwargs) + + @classmethod + def from_c3d( + cls, + filename: Union[str, Path], + usecols: Optional[List[Union[str, int]]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + attrs: Optional[dict] = None, + ) -> xr.DataArray: + """ + Analogs DataArray from a c3d file. + + Arguments: + filename: Any valid string path + usecols: All elements must either be positional or strings that correspond to column names. + For example, a valid list-like usecols parameter would be [0, 1, 2] or ['foo', 'bar', 'baz']. + prefix_delimiter: Delimiter that split each column name by its prefix (we keep only the column name) + suffix_delimiter: Delimiter that split each column name by its suffix (we keep only the column name) + attrs: attrs to be passed to xr.DataArray + + Returns: + Analogs `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this c3d file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers_analogs.c3d), + type: + + ```python + from pyomeca import Analogs + + data_path = "./tests/data/markers_analogs.c3d" + analogs = Analogs.from_c3d(data_path) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in: + + ```python + channels = ["Voltage.1", "Voltage.2", "Voltage.3"] + analogs = Analogs.from_c3d(data_path, usecols=channels) + ``` + + Sometimes the channel name is delimited by a suffix or prefix. + To access the prefix, you can specify `prefix_delimiter` and `suffix_delimiter` for the suffix. + For example, if the name is `"Voltage.1"` and you specify `suffix_delimiter="."`, you will select "Voltage". + Similarly, if you specify `prefix_delimiter=".": + + ```python + channels = ["1", "2", "3"] + analogs = Analogs.from_c3d(data_path, usecols=channels, prefix_delimiter=".") + ``` + """ + return read.read_c3d( + cls, filename, usecols, prefix_delimiter, suffix_delimiter, attrs + ) + + @staticmethod + def _reshape_flat_array(array: Union[np.array, np.ndarray]) -> xr.DataArray: + """ + Takes a tabular numpy array (frames x N) and return a (N x frames) numpy array + + Arguments: + array: A tabular array (frames x N) with N = 3 x marker + + Returns: + Reshaped Analogs `xarray.DataArray` """ - seconds = int(time * self.trials[0].get_rate) - mva = [] - for imuscle, values in self.concatenated.items(): - if not np.isnan(values).all(): - sorted_values = np.sort(values) - mu = np.nanmean(sorted_values[-seconds:]) - mva.append(mu) - - if self.plot_mva: - plt.plot(sorted_values[-seconds:], "b-", label="sorted activation") - plt.axhline(y=mu, c="k", ls="--", label="MVA") - plt.title(f"Last {time} seconds | {self.channels[0][imuscle]}") - plt.legend() - plt.show() + return array.T + + @staticmethod + def _get_requested_channels_from_pandas( + columns, header, usecols, prefix_delimiter: str, suffix_delimiter: str + ) -> Tuple[Optional[list], Optional[list]]: + if usecols: + idx, channels = [], [] + if isinstance(usecols[0], int): + for i in usecols: + idx.append(i) + channels.append( + utils.col_spliter( + columns[i], prefix_delimiter, suffix_delimiter + ) + ) + elif isinstance(usecols[0], str): + columns_split = [ + utils.col_spliter(col, prefix_delimiter, suffix_delimiter) + for col in columns + ] + for col in usecols: + idx.append(columns_split.index(col)) + channels.append(col) else: - mva.append(None) - return mva + raise ValueError( + "usecols should be None, list of string or list of int." + f"You provided {type(usecols)}" + ) + return channels, idx + + if header is None: + return None, None + + channels = [ + utils.col_spliter(col, prefix_delimiter, suffix_delimiter) + for col in columns + ] + return channels, None diff --git a/pyomeca/angles.py b/pyomeca/angles.py new file mode 100644 index 0000000..636a18e --- /dev/null +++ b/pyomeca/angles.py @@ -0,0 +1,128 @@ +from typing import Optional, Union + +import numpy as np +import pandas as pd +import xarray as xr + +from pyomeca.processing import angles + + +class Angles: + def __new__( + cls, + data: Optional[Union[np.array, np.ndarray, xr.DataArray]] = None, + time: Optional[Union[np.array, list, pd.Series]] = None, + **kwargs, + ) -> xr.DataArray: + """ + Angles DataArray with `axis`, `channel` and `time` dimensions used for joint angles. + ![angles](/images/objects/angles.svg) + + Arguments: + data: Array to be passed to xarray.DataArray + time: Time vector in seconds associated with the `data` parameter + kwargs: Keyword argument(s) to be passed to xarray.DataArray + + Returns: + Angles `xarray.DataArray` with the specified data and coordinates + + !!! example + To instantiate an `Angles` 3 by 3 and 100 frames filled with some random data: + + ```python + import numpy as np + from pyomeca import Angles + + n_axis = 3 + n_channel = 4 + n_frames = 100 + data = np.random.random(size=(n_axis, n_channel, n_frames)) + angles = Angles(data) + ``` + + You can an associate time vector: + + ```python + rate = 100 # Hz + time = np.arange(start=0, stop=n_frames / rate, step=1 / rate) + angles = Angles(data, time=time) + ``` + + !!! note + Calling `Angles()` generate an empty array. + """ + coords = {} + if data is None: + data = np.ndarray((0, 0, 0)) + if time is not None: + coords["time"] = time + return xr.DataArray( + data=data, + dims=("axis", "channel", "time"), + coords=coords, + name="angles", + **kwargs, + ) + + @classmethod + def from_random_data( + cls, distribution: str = "normal", size: tuple = (3, 10, 100), **kwargs + ) -> xr.DataArray: + """ + Create random data from a specified distribution (normal by default) using random walk. + + Arguments: + distribution: Distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions) + size: Shape of the desired array + kwargs: Keyword argument(s) to be passed to numpy.random.`distribution` + + Returns: + Random angles `xarray.DataArray` sampled from a given distribution + + !!! example + To instantiate an `Angles` with some random data sampled from a normal distribution: + + ```python + from pyomeca import Angles + + n_frames = 100 + size = 3, 10, n_frames + angles = Angles.from_random_data(size=size) + ``` + + You can choose any distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions): + + ```python + angles = Angles.from_random_data(distribution="uniform", size=size, low=1, high=10) + ``` + """ + return Angles(getattr(np.random, distribution)(size=size, **kwargs).cumsum(-1)) + + @classmethod + def from_rototrans(cls, rt: xr.DataArray, angle_sequence: str) -> xr.DataArray: + """ + Angles DataArray from a rototranslation matrix and specified angle sequence. + + Arguments: + rt: Rototranslation matrix created with pyomeca.Rototrans() + angle_sequence: Euler sequence of angles. Valid values are all permutations of "xyz" + + Returns: + Angles `xarray.DataArray` from the specified rototrans and angles sequence + + !!! example + To get the euler angles from a random rototranslation matrix with a given angle sequence type: + + ```python + from pyomeca import Angles, Rototrans + + size = (4, 4, 100) + rt = Rototrans.from_random_data(size=size) + angles_sequence = "xyz" + + angles = Angles.from_rototrans(rt, angles_sequence) + ``` + """ + return angles.angles_from_rototrans(cls, rt, angle_sequence) diff --git a/pyomeca/dataarray_accessor.py b/pyomeca/dataarray_accessor.py new file mode 100644 index 0000000..c56358c --- /dev/null +++ b/pyomeca/dataarray_accessor.py @@ -0,0 +1,757 @@ +from pathlib import Path +from typing import Optional, Union + +import numpy as np +import pandas as pd +import xarray as xr + +from pyomeca.io import write +from pyomeca.processing import filter, interp, matrix, misc, rototrans + + +@xr.register_dataarray_accessor("meca") +class DataArrayAccessor(object): + """Meca DataArray accessor used for processing or file writing.""" + + def __init__(self, xarray_obj: xr.DataArray): + self._obj = xarray_obj + + # io ---------------------------------------- + def to_matlab(self, filename: Union[str, Path]): + """ + Write a matlab file from a xarray.DataArray. + + Arguments: + filename: File path + + !!! example + To write a matlab file from any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.to_matlab(filename="temp.mat") + ``` + """ + write.write_matlab(self._obj, filename) + + def to_csv(self, filename: Union[str, Path], wide: Optional[bool] = True): + """ + Write a csv file from a xarray.DataArray. + + Arguments: + filename: File path + wide: True if you want a wide dataframe (one column for each channel). False if you want a tidy dataframe. + + !!! example + To write a csv file from any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.to_csv(filename="temp.csv") + ``` + + By default, `to_csv` will export the data in a "wide" format (1 column by channel). + You can also export the data in a "tidy" format with `wide=False`: + + ```python + analogs.meca.to_csv(filename="temp.csv", wide=False) + ``` + """ + write.write_csv(self._obj, filename, wide) + + def to_wide_dataframe(self) -> pd.DataFrame: + """ + Return a wide pandas.DataFrame (one column by channel). + + Returns: + A wide pandas DataFrame (one column by channel). + Works only for 2 and 3-dimensional arrays. + If you want a tidy dataframe type: `array.to_series()`, or `array.to_dataframe()`. + + !!! example + To return a dataframe from any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.to_wide_dataframe() + ``` + """ + return write.to_wide_dataframe(self._obj) + + # matrix ----------------------------------- + + def abs(self) -> xr.DataArray: + """ + Calculate the absolute value element-wise. + + Returns: + A `xarray.DataArray` containing the absolute of each element + + !!! example + To compute the absolute value of any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.abs() + ``` + """ + return matrix.abs_(self._obj) + + def matmul(self, other: xr.DataArray) -> xr.DataArray: + """ + Matrix product of two arrays. + + Arguments: + other: second array to multiply + + Returns: + A `xarray.DataArray` containing the matrix product of the two arrays + + !!! example + To compute the matrix product of two `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + first_analogs = Analogs.from_random_data() + second_analogs = Analogs.from_random_data() + + first_analogs.meca.matmul(second_analogs) + ``` + + You can also use the shorthand `@`: + + ```python + first_analogs @ second_analogs + ``` + """ + return matrix.matmul(self._obj, other) + + def square(self, **kwargs) -> xr.DataArray: + """ + Return the element-wise square of the input. + + Arguments: + kwargs: For other keyword-only arguments, + see the [numpy docs](https://docs.scipy.org/doc/numpy/reference/generated/numpy.square.html) + + Returns: + A `xarray.DataArray` containing the matrix squared. + + !!! example + To compute the element-wise square of any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.square() + ``` + """ + return matrix.square(self._obj, **kwargs) + + def sqrt(self, **kwargs) -> xr.DataArray: + """ + Return the non-negative square-root of an array, element-wise. + + Arguments: + kwargs: For other keyword-only arguments, + see the [numpy docs](https://docs.scipy.org/doc/numpy/reference/generated/numpy.sqrt.html) + + Returns: + A `xarray.DataArray` containing the square root of the matrix. + + !!! example + To compute the non-negative square-root of any `xarray.DataArray` + (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.sqrt() + ``` + """ + return matrix.sqrt(self._obj, **kwargs) + + def norm(self, dim: Union[str, list], ord: int = None) -> xr.DataArray: + """ + Return the norm of an array. + + Arguments: + dim: Name(s) of the data dimension(s) + ord: Order of the norm + + Returns: + A `xarray.DataArray` containing the norm of the matrix. + + !!! example + To compute the norm of any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans) + along a given dimension: + + ```python + from pyomeca import Markers + + markers = Markers.from_random_data() + markers.meca.norm(dim="axis") + ``` + + Note: + If the array contains an `"axis"` dimension with a `"ones"` coordinate + (e.g., the object was created using `pyomeca.Markers`), this coordinate is ignored. + + """ + return matrix.norm(self._obj, dim, ord) + + def rms(self) -> xr.DataArray: + """ + Return the root-mean-square of an array. + + Returns: + A `xarray.DataArray` containing the root-mean-square of the matrix. + + !!! example + To compute the root-mean-square of any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.rms() + ``` + """ + return matrix.rms(self._obj) + + def center( + self, mu: Union[xr.DataArray, np.array, float, int] = None + ) -> xr.DataArray: + """ + Center an array (i.e., subtract the mean). + + Arguments: + mu: the value to be subtracted. If unspecified, take the mean along the time axis. + + Returns: + a `xarray.DataArray` containing the root-mean-square of the matrix + + !!! example + To center any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + import numpy as np + + from pyomeca import Analogs + + random_data = np.random.uniform(low=2, high=4, size=(1, 100)) + analogs = Analogs(random_data) + centered = analogs.meca.center() + ``` + + This will substract the mean of the signal by default. + The previous random signal was sampled from a uniform distribution from 2 and 4 (mean around 3). + When centered, the signal is now center around 0 (mean around 0). + + ```python + import matplotlib.pyplot as plt + + analogs.plot(label="raw") + centered.plot(label="centered") + plt.legend() + plt.show() + ``` + + ![center](/images/api/center.svg) + """ + return matrix.center(self._obj, mu) + + def normalize( + self, + ref: Union[xr.DataArray, np.array, float, int] = None, + scale: Union[int, float] = 100, + ) -> xr.DataArray: + """ + Normalize a signal against `ref` on a scale of `scale`. + + Arguments: + ref: Reference value. Could have multiple dimensions. + If not provided, takes the mean along the time axis + scale: Scale on which to express array (e.g. if 100, the signal is normalized from 0 to 100) + Returns: + A `xarray.DataArray` containing the normalized signal + + !!! example + To normalize any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + import matplotlib.pyplot as plt + + from pyomeca import Analogs + + analogs = Analogs.from_random_data(size=(1, 100)).meca.abs() + normalized = analogs.meca.normalize() + + normalized.plot() + plt.show() + ``` + + ![normalize](/images/api/normalize.svg) + + By default, this function normalize against the signal's max. + To specify any other value, use the `ref` parameter: + + ```python + normalized = analogs.meca.normalize(ref=1) + ``` + """ + return matrix.normalize(self._obj, ref, scale) + + # interp ------------------------------------ + def time_normalize( + self, + time_vector: Union[xr.DataArray, np.array] = None, + n_frames: int = 100, + norm_time: bool = False, + ) -> xr.DataArray: + """ + Time normalization used for temporal alignment of data. + + Arguments: + time_vector: desired time vector (first to last time with n_frames points by default) + n_frames: if time_vector is not specified, the length of the desired time vector + norm_time: Normalize the time dimension from 0 to 100 if True + + Returns: + A time-normalized `xarray.DataArray` + + !!! example + To time-normalize any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans): + + ```python + import matplotlib.pyplot as plt + + from pyomeca import Analogs + + analogs = Analogs.from_random_data(size=(1, 847)) + time_normalized = analogs.meca.time_normalize() + print(time_normalized.time.size) # 100 + ``` + + To normalize the corresponding time dimension from 0 to 100%, specify `norm_time=True`: + + ```python + time_normalized = analogs.meca.time_normalize(norm_time=True) + time_normalized.plot() + plt.show() + ``` + + ![time_normalize](/images/api/time_normalize.svg) + + By default, `time_normalize` use a time vector with 100 frames from 0 to 100. + However, you can specify the desired number of frames: + + ```python + time_normalized = analogs.meca.time_normalize(n_frames=500) + print(time_normalized.time.size) # 500 + ``` + + You can also specify the desired time_vector directly in the `time_vector` parameter: + + ```python + import numpy as np + + time_normalized = analogs.meca.time_normalize(time_vector=np.linspace(0, 200, 300)) + ``` + """ + return interp.time_normalize( + self._obj, time_vector, n_frames, norm_time=norm_time + ) + + # filter ------------------------------------ + def low_pass( + self, + order: int, + cutoff: Union[int, float, np.array], + freq: Optional[Union[int, float]] = None, + ) -> xr.DataArray: + """ + Low-pass Butterworth filter. + + Arguments: + order: Order of the filter + cutoff: Cut-off frequency + freq: Sampling frequency. + Optional if attrs["rate"] is specified. + + Returns: + A low-pass filtered `xarray.DataArray` + + !!! example + To low-pass any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans) signal at 5Hz: + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + analogs.meca.low_pass(order=2, cutoff=5, freq=100) + ``` + + Let's see how the low-pass smooth a fake sinusoidal signal: + + ```python + import matplotlib.pyplot as plt + import numpy as np + + # generate fake data + freq = 100 # Hz + time_vector = np.linspace(start=0, stop=100, num=100) + w = 2 * np.pi * 1 + y = np.sin(w * time_vector) + 0.1 * np.sin(10 * w * time_vector) + + analogs = Analogs(y.reshape(1, -1)) + low_pass = analogs.meca.low_pass(order=2, cutoff=5, freq=freq) + + analogs.plot(label="raw") + low_pass.plot(label="low-pass @ 5Hz") + plt.legend() + plt.show() + ``` + + ![low_pass](/images/api/low_pass.svg) + """ + return filter.low_pass(self._obj, order, cutoff, freq) + + def high_pass( + self, + order: int, + cutoff: Union[int, float, np.array], + freq: Optional[Union[int, float]] = None, + ) -> xr.DataArray: + """ + High-pass Butterworth filter. + + Arguments: + order: Order of the filter + cutoff: Cut-off frequency + freq: Sampling frequency. + Optional if attrs["rate"] is specified. + + Returns: + A high-pass filtered `xarray.DataArray` + + !!! example + To high-pass any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans) signal at 100Hz: + + ```python + import matplotlib.pyplot as plt + import numpy as np + + from pyomeca import Analogs + + fake_emg = np.random.uniform(low=-1, high=1, size=(1, 1000)) + analogs = Analogs(fake_emg) + freq = 1000 # Hz + high_pass = analogs.meca.high_pass(order=2, cutoff=100, freq=freq) + + analogs.plot(label="raw") + high_pass.plot(label="high-pass @ 100Hz") + plt.legend() + plt.show() + ``` + + ![high_pass](/images/api/high_pass.svg) + """ + return filter.high_pass(self._obj, order, cutoff, freq) + + def band_stop( + self, + order: int, + cutoff: Union[list, tuple, np.array], + freq: Optional[Union[int, float]] = None, + ) -> xr.DataArray: + """ + Band-stop Butterworth filter. + + Arguments: + order: Order of the filter + cutoff: Cut-off frequency such as (lower, upper) + freq: Sampling frequency. + Optional if attrs["rate"] is specified. + + Returns: + A band-stop filtered `xarray.DataArray` + + !!! example + To band-stop any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans) signal at 40-60Hz: + + ```python + import matplotlib.pyplot as plt + import numpy as np + + from pyomeca import Analogs + + fake_emg = np.random.uniform(low=-1, high=1, size=(1, 1000)) + analogs = Analogs(fake_emg) + freq = 1000 # Hz + band_stop = analogs.meca.band_stop(order=2, cutoff=[40, 60], freq=freq) + + analogs.plot(label="raw") + band_stop.plot(label="band-stop @ 40-60Hz") + plt.legend() + plt.show() + ``` + + ![band_stop](/images/api/band_stop.svg) + + !!! note + You can also perform a notch filter with this method. + A notch filter is a band-stop filter with a narrow bandwidth. + It rejects a narrow frequency band and leaves the rest of the spectrum little changed. + """ + return filter.band_stop(self._obj, order, cutoff, freq) + + def band_pass( + self, + order: int, + cutoff: Union[list, tuple, np.array], + freq: Optional[Union[int, float]] = None, + ) -> xr.DataArray: + """ + Band-pass Butterworth filter. + + Arguments: + order: Order of the filter + cutoff: Cut-off frequency such as (lower, upper) + freq: Sampling frequency. + Optional if attrs["rate"] is specified. + + Returns: + A band-pass filtered `xarray.DataArray` + + !!! example + To band-pass any `xarray.DataArray` (including Analogs, Angles, Markers or Rototrans) signal at 10-200Hz: + + ```python + import matplotlib.pyplot as plt + import numpy as np + + from pyomeca import Analogs + + fake_emg = np.random.uniform(low=-1, high=1, size=(1, 1000)) + analogs = Analogs(fake_emg) + freq = 1000 # Hz + band_pass = analogs.meca.band_pass(order=2, cutoff=[10, 200], freq=freq) + + analogs.plot(label="raw") + band_pass.plot(label="band-pass @ 10-200Hz") + plt.legend() + plt.show() + ``` + + ![band_pass](/images/api/band_pass.svg) + """ + return filter.band_pass(self._obj, order, cutoff, freq) + + # signal processing misc -------------------- + def fft(self, freq: Union[int, float], only_positive: bool = True) -> xr.DataArray: + """ + Performs a discrete Fourier Transform and return a DataArray with the corresponding amplitudes and frequencies. + + Arguments: + freq: Sampling frequency (usually stored in `array.attrs["rate"]`) + only_positive: If `True`, returns only the positive frequencies + + Returns: + A `xarray.DataArray` with the corresponding amplitudes and frequencies + + !!! example + Let's compare the resulting fft on a raw and low-passed signal: + + ```python + import matplotlib.pyplot as plt + import numpy as np + + from pyomeca import Analogs + + # generate fake data + freq = 100 + time = np.arange(0, 1, 0.01) + w = 2 * np.pi + y = np.sin(w * time) + 0.1 * np.sin(10 * w * time) + + analogs = Analogs(y.reshape(1, -1)) + analogs_low_passed = analogs.meca.low_pass(order=2, cutoff=5, freq=freq) + + # compute fft on raw and low-passed signal + fft_raw = analogs.meca.fft(freq=freq) + fft_low_passed = analogs_low_passed.meca.fft(freq=freq) + + fig, ax = plt.subplots(ncols=2, figsize=(10, 4)) + + # plot signal vs. time + analogs.plot.line(x="time", ax=ax[0], color="black", add_legend=False) + analogs_low_passed.plot.line(x="time", ax=ax[0], color="red", add_legend=False) + ax[0].set_title("Signal vs. Time") + + # plot amplitudes vs. frequencies + fft_raw.plot.line(x="freq", ax=ax[1], color="black", label="raw") + fft_low_passed.plot.line(x="freq", ax=ax[1], color="red", label="low-pass @ 5Hz") + ax[1].set_title("Amplitudes vs. Freq") + + plt.legend() + plt.show() + ``` + + ![fft](/images/api/fft.svg) + """ + return misc.fft(self._obj, freq, only_positive) + + def detect_onset( + self, + threshold: Union[float, int], + n_above: int = 1, + n_below: int = 0, + threshold2: int = None, + n_above2: int = 1, + ) -> np.array: + """ + Detects onset based on amplitude threshold. + + Arguments: + threshold: minimum amplitude to detect + n_above: minimum number of continuous samples >= `threshold` to detect + n_below: minimum number of continuous samples below `threshold` + that will be ignored in the detection of `x` >= `threshold` + threshold2: minimum amplitude of `n_above2` values in `x` to detect + n_above2: minimum number of samples >= `threshold2` to detect + + Note: + You might have to tune the parameters according to the signal-to-noise + characteristic of the data. + + Returns: + inds: 1D array_like [indi, indf] containing initial and final indexes of the onset events + + !!! example + To detect the onsets of any __one-dimensional__ `xarray.DataArray` + (including Analogs, Angles, Markers or Rototrans): + + ```python + import matplotlib.pyplot as plt + import numpy as np + import scipy.signal as sig + + from pyomeca import Analogs + + # simulate fake ecg data + rr = 2.5 # rr time in seconds + freq = 100 # sampling rate + pqrst = sig.resample(sig.wavelets.daub(10), int(rr * freq)) + ecg = np.concatenate([pqrst, pqrst, pqrst]).reshape(1, -1) + + analogs = Analogs(ecg) + analogs.plot() + + onsets = analogs.sel(channel=0).meca.detect_onset( + threshold=analogs.mean(), # mean of the signal + n_above=freq / 2, # we want at least 1/2 second above the threshold + n_below=freq / 2, # we accept point below threshold for 1/2 second + ) + for (start, end) in onsets: + plt.axvline(x=start, color="g") + plt.axvline(x=end, color="r") + + plt.show() + ``` + + ![detect_onset](/images/api/detect_onset.svg) + + !!! warning + `detect_onset` works only for 1-dimensional data. + For example, you can select a dimension using `analogs.sel(channel='EMG1')` or `analogs.isel(channel=0)`. + """ + return misc.detect_onset( + self._obj, threshold, n_above, n_below, threshold2, n_above2 + ) + + def detect_outliers(self, threshold: int = 3) -> xr.DataArray: + """ + Detects data points that are `threshold` times the standard deviation from the mean. + + Arguments: + threshold: Multiple of standard deviation from which data is considered outlier + + Returns: + A boolean `xarray.DataArray` containing the outliers + + !!! example + To get a boolean `xr.DataArray` containing the data that are 3 times the mean +/- standard deviation: + + ```python + from pyomeca import Analogs + + analogs = Analogs.from_random_data() + outliers = analogs.meca.detect_outliers(threshold=1) + ``` + + Let's plot the data that are 1 time the mean +/- standard deviation on an analog vector: + + ```python + import matplotlib.pyplot as plt + + analogs = Analogs.from_random_data(size=(1, 100)) + + threshold = 1 + outliers = analogs.meca.detect_outliers(threshold=threshold) + + analogs.plot.line(x="time", color="black", add_legend=False) + analogs.where(outliers).plot.line( + x="time", color="red", add_legend=False, marker="o", label="outliers" + ) + + mu = analogs.mean() + sigma = analogs.std() + + plt.axhline(mu, color="grey") + plt.axhspan( + mu - threshold * sigma, + mu + threshold * sigma, + color="grey", + alpha=0.3, + label=f"mean +/- {threshold} std", + ) + + plt.legend() + plt.show() + ``` + + ![detect_outliers](/images/api/detect_outliers.svg) + + Note: + `detect_outliers` is not limited on one-dimensional data and + can detect outliers for any number of dimensions. + """ + return misc.detect_outliers(self._obj, threshold) + + @property + def rotation(self) -> xr.DataArray: + return rototrans.rotation_getter(self._obj) + + @rotation.setter + def rotation(self, value) -> None: + rototrans.rotation_setter(self._obj, value) + + @property + def translation(self) -> xr.DataArray: + return rototrans.translation_getter(self._obj) + + @translation.setter + def translation(self, value) -> None: + rototrans.translation_setter(self._obj, value) diff --git a/pyomeca/frame_dependent.py b/pyomeca/frame_dependent.py deleted file mode 100644 index f9a1d77..0000000 --- a/pyomeca/frame_dependent.py +++ /dev/null @@ -1,1360 +0,0 @@ -from pathlib import Path - -import ezc3d -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from scipy import fftpack -from scipy.interpolate import interp1d -from scipy.io import savemat -from scipy.signal import filtfilt, medfilt, butter - - -class FrameDependentNpArray(np.ndarray): - def __new__(cls, array=np.ndarray((0, 0, 0)), *args, **kwargs): - """ - Convenient wrapper around np.ndarray for time related data - Parameters - ---------- - array : np.ndarray - A 3-dimensions matrix where 3rd dimension is the frame - ------- - - """ - if not isinstance(array, np.ndarray): - raise TypeError("FrameDependentNpArray must be a numpy array") - - # Sanity check on size - if len(array.shape) == 1: - array = array[:, np.newaxis, np.newaxis] - if len(array.shape) == 2: - array = array[:, :, np.newaxis] - - # metadata - obj = np.asarray(array).view(cls, *args, **kwargs) - obj.__array_finalize__(array) - return obj - - def __parse_item__(self, item): - if isinstance(item, int): - pass - elif isinstance(item[0], str): - if len(self.shape) != 3: - raise RuntimeError( - "Name slicing is only valid on 3D FrameDependentNpArray" - ) - item = ( - slice(None, None, None), - self.get_index(item), - slice(None, None, None), - ) - elif len(item) == 3: - if isinstance(item[1], int): - if isinstance(item[0], int) and isinstance(item[2], int): - pass - else: - item = (item[0], [item[1]], item[2]) - if isinstance(item[1], tuple): - item = (item[0], list(item[1]), item[2]) - if isinstance(item[1], list): # If multiple value - idx = self.get_index(item[1]) - if idx: - # Replace the text by number so it can be sliced - idx_str = [i for i, it in enumerate(item[1]) if isinstance(it, str)] - for i1, i2 in enumerate(idx_str): - item[1][i2] = idx[i1] - elif isinstance(item[1], str): # If single value - item = (item[0], self.get_index(item[1]), item[2]) - return item - - def __getitem__(self, item): - return super(FrameDependentNpArray, self).__getitem__(self.__parse_item__(item)) - - def __setitem__(self, key, value): - return super(FrameDependentNpArray, self).__setitem__( - self.__parse_item__(key), value - ) - - def __array_finalize__(self, obj): - # Allow slicing - if obj is None or not isinstance(obj, FrameDependentNpArray): - self._current_iter_idx = 0 - self.get_first_frame = [] - self.get_last_frame = [] - self.get_time_frames = None - self.get_rate = [] - self.get_labels = [] - self.get_unit = [] - self.get_nan_idx = None - self.misc = {} - else: - self._current_iter_idx = getattr(obj, "_current_iter_idx") - self.get_first_frame = getattr(obj, "get_first_frame") - self.get_last_frame = getattr(obj, "get_last_frame") - self.get_time_frames = getattr(obj, "get_time_frames") - self.get_rate = getattr(obj, "get_rate") - self.get_labels = getattr(obj, "get_labels") - self.get_unit = getattr(obj, "get_unit") - self.get_nan_idx = getattr(obj, "get_nan_idx") - self.misc = getattr(obj, "misc") - - def dynamic_child_cast(self, x): - """ - Dynamically cast the np.array into type of self (which is probably inherited from FrameDependentNpArray) - Parameters - ---------- - x : np.array - - Returns - ------- - x in the same type as self - """ - casted_x = type(self)(x) - casted_x.__array_finalize__(self) - return casted_x - - def __iter__(self): - self._current_iter_idx = 0 # Reset the counter - return self - - def __next__(self): - self._current_iter_idx += 1 - if len(self.shape) == 1: - if self._current_iter_idx < self.shape[0]: - return self[self._current_iter_idx] - else: - raise StopIteration - elif len(self.shape) == 2: - if self._current_iter_idx < self.shape[1]: - return self[self._current_iter_idx, :] - else: - raise StopIteration - elif len(self.shape) == 3: - if self._current_iter_idx < self.shape[2]: - return self.get_frame( - self._current_iter_idx - 1 - ) # -1 since it is incremented before hand - else: - raise StopIteration - - # --- Utils methods - - @classmethod - def _get_class_name(cls): - return cls.__name__ - - @staticmethod - def check_parent_dir(file_name): - file_name = Path(file_name) - if not file_name.parents[0].is_dir(): - file_name.parents[0].mkdir() - return file_name - - def update_misc(self, d): - """ - Append the misc field with a given dictionary. - An Optional reference to the internal state is also return in order to chain the operation if needed. - - Parameters - ---------- - d : dict - Dictionary to be added to the misc field - """ - self.misc.update(d) - return self.dynamic_child_cast(self) - - # --- Get metadata methods - - def get_num_frames(self): - """ - - Returns - ------- - The number of frames - """ - s = self.shape - if len(s) == 2: - return 1 - else: - return s[2] - - def get_frame(self, f): - """ - Return the fth frame of the array - Parameters - ---------- - f : int - index of frame - - Returns - ------- - frame - """ - return self[..., f] - - def get_index(self, names): - """ - Return the index associated to label names - Parameters - ---------- - names : list(str) - names of the label to find the indexes of - - Returns - ------- - indexes - """ - if isinstance(names, list) or isinstance(names, tuple): - # Remove the integer - names = [n for n in names if isinstance(n, str)] - else: - names = [names] - - if not names: # If all integer, return null - return [] - else: - return [self.get_labels.index(name) for name in names] - - # --- Fileio methods (from_*) - - @classmethod - def from_excel( - cls, - filename, - sheet_name=None, - first_row=0, - time_column=None, - first_column=None, - last_column_to_remove=None, - idx=None, - header=None, - names=None, - prefix=None, - skiprows=None, - na_values=None, - ): - """ - Read Excel data and transform it in vector3d format - - Parameters - ---------- - caller : str - if the caller is the from_csv (`csv`) or from_excel (`excel`) method - filename : Union[str, Path] - Path of file - sheet_name: Union[str, int, list] - Strings are used for sheet names. Integers are used in zero-indexed sheet positions. Lists of strings/integers are used to request multiple sheets. Specify None to get all sheets. - first_row : int - Index of first rows of data (0th indexed) - first_column : int - Index of first column of data (0th indexed) - last_column_to_remove : int - If for some reason the csv reads extra columns, how many should be ignored - time_column : int - Index of the time column, if None time column is the index - idx : list(int) - Order of columns given by index - header : int - row of the header (0th indexed) - names : list(str) - Order of columns given by names, if both names and idx are provided, an error occurs - delimiter : str - Delimiter of the CSV file - prefix : str - Prefix to remove in the header - - Returns - ------- - Data set in Vectors3d format - """ - return cls._from_csv_or_excel( - caller="excel", - filename=filename, - sheet_name=sheet_name, - first_row=first_row, - time_column=time_column, - first_column=first_column, - last_column_to_remove=last_column_to_remove, - idx=idx, - header=header, - names=names, - prefix=prefix, - skiprows=skiprows, - na_values=na_values, - ) - - @classmethod - def from_csv( - cls, - filename, - first_row=0, - time_column=None, - first_column=None, - last_column_to_remove=None, - idx=None, - header=None, - names=None, - delimiter=",", - prefix=None, - skiprows=None, - na_values=None, - ): - """ - Read csv data and convert to Vectors3d format - - Parameters - ---------- - filename : Union[str, Path] - Path of file - first_row : int - Index of first rows of data (0th indexed) - first_column : int - Index of first column of data (0th indexed) - last_column_to_remove : int - If for some reason the csv reads extra columns, how many should be ignored - time_column : int - Index of the time column, if None time column is the index - idx : list(int) - Order of columns given by index - header : int - row of the header (0th indexed) - names : list(str) - Order of columns given by names, if both names and idx are provided, an error occurs - delimiter : str - Delimiter of the CSV file - prefix : str - Prefix to remove in the header - - Returns - ------- - Data set in Vectors3d format - """ - return cls._from_csv_or_excel( - caller="csv", - filename=filename, - first_row=first_row, - time_column=time_column, - first_column=first_column, - last_column_to_remove=last_column_to_remove, - idx=idx, - header=header, - names=names, - delimiter=delimiter, - prefix=prefix, - skiprows=skiprows, - na_values=na_values, - ) - - @classmethod - def _from_csv_or_excel( - cls, - caller, - filename, - sheet_name=0, - first_row=0, - time_column=None, - first_column=None, - last_column_to_remove=None, - idx=None, - header=None, - names=None, - delimiter=",", - prefix=None, - skiprows=None, - na_values=None, - ): - """ - Private function to parse pandas based data to vector3d format - - Parameters - ---------- - caller : str - if the caller is the from_csv (`csv`) or from_excel (`excel`) method - filename : Union[str, Path] - Path of file - sheet_name: Union[str, int, list] - Strings are used for sheet names. Integers are used in zero-indexed sheet positions. Lists of strings/integers are used to request multiple sheets. Specify None to get all sheets. - first_row : int - Index of first rows of data (0th indexed) - first_column : int - Index of first column of data (0th indexed) - last_column_to_remove : int - If for some reason the csv reads extra columns, how many should be ignored - time_column : int, str - Index or string of the time column, if None time column is the index - idx : list(int) - Order of columns given by index - header : int - row of the header (0th indexed) - names : list(str) - Order of columns given by names, if both names and idx are provided, an error occurs - delimiter : str - Delimiter of the CSV file - prefix : str - Prefix to remove in the header - - Returns - ------- - Data set in Vectors3d format - """ - - if names and idx: - raise ValueError( - "names and idx can't be set simultaneously, please select only one" - ) - - if not skiprows: - if not header: - skiprows = np.arange(1, first_row) - else: - skiprows = np.arange(header + 1, first_row) - - if caller == "csv": - data = pd.read_csv( - filename, - sep=delimiter, - header=header, - skiprows=skiprows, - na_values=na_values, - ) - else: - data = pd.read_excel( - filename, - sheet_name=sheet_name, - header=header, - skiprows=skiprows, - na_values=na_values, - ) - - if time_column is None: - time_frames = np.arange(0, data.shape[0]) - else: - if isinstance(time_column, int): - time_frames = data.iloc[:, time_column].values - data = data.drop(data.columns[time_column], axis=1) - else: - time_frames = data.loc[:, time_column].values - data = data.drop(time_column, axis=1) - - if first_column: - data.drop(data.columns[:first_column], axis=1, inplace=True) - - if last_column_to_remove: - data.drop(data.columns[-last_column_to_remove], axis=1, inplace=True) - - column_names = data.columns.tolist() - if header and cls._get_class_name()[:9] == "Markers3d": - column_names = [ - icol.split(prefix)[-1] - for icol in column_names - if not (len(icol) >= 7 and icol[:7] == "Unnamed") - ] - metadata = { - "get_first_frame": [], - "get_last_frame": [], - "get_rate": [], - "get_labels": [], - "get_unit": [], - "get_time_frames": time_frames, - } - if names: - metadata.update({"get_labels": names}) - else: - names = column_names - - return cls._to_vectors( - data=data.values, - idx=idx, - all_names=column_names, - target_names=names, - metadata=metadata, - ) - - @staticmethod - def _parse_c3d(c3d, prefix): - """ - Abstract function on how to read c3d header and parameter for markers or analogs. - Must be implemented for each subclasses of frame_dependent. - - Parameters - ---------- - c3d : ezc3d class - Pointer on the read c3d - prefix : str, optional - Participant's prefix - Returns - ------- - data : np.ndarray - Actual data - channel_names : List(string) - Name of the channels - metadata - Structure of properties in the c3d files - """ - raise NotImplementedError("_parse_c3d_info is an abstract function") - - @classmethod - def from_c3d(cls, filename, idx=None, names=None, prefix=None): - """ - Read c3d data and convert to Vectors3d format - Parameters - ---------- - filename : Union[str, Path] - Path of file - idx : list(int) - Order of columns given by index - names : list(str) - Order of columns given by names, if both names and idx are provided, an error occurs - prefix : str - Prefix to remove in the header - - Returns - ------- - Data set in Vectors3d format or Data set in Vectors3d format and metadata dict if get_metadata is True - """ - if names and idx: - raise ValueError( - "names and idx can't be set simultaneously, please select only one" - ) - reader = ezc3d.c3d(str(filename)).c3d_swig - data, channel_names, metadata = cls._parse_c3d(reader, prefix) - - if names: - metadata.update({"get_labels": names}) - else: - metadata.update({"get_labels": []}) - names = channel_names - - # Add time frames - metadata["get_time_frames"] = np.arange( - metadata["get_first_frame"] / metadata["get_rate"], - (metadata["get_last_frame"] + 1) / metadata["get_rate"], - 1 / metadata["get_rate"], - ) - - return cls._to_vectors( - data=data, - idx=idx, - all_names=channel_names, - target_names=names, - metadata=metadata, - ) - - @classmethod - def _to_vectors(cls, data, idx, all_names, target_names, metadata=None): - if not idx: - idx = [all_names.index(itarget) for itarget in target_names] - - data = cls.__new__(cls, data) # Dynamically cast the data to fit the child - data = data.get_specific_data(idx) - - data.get_first_frame = metadata["get_first_frame"] - data.get_last_frame = metadata["get_last_frame"] - data.get_rate = metadata["get_rate"] - data.get_unit = metadata["get_unit"] - data.get_time_frames = metadata["get_time_frames"] - if np.array(idx).ndim == 1 and not metadata["get_labels"]: - data.get_labels = [name for i, name in enumerate(all_names) if i in idx] - elif metadata["get_labels"]: - data.get_labels = metadata["get_labels"] - return data - - def get_specific_data(self, idx): - """ - # TODO: description - Parameters - ---------- - idx : list(int) - idx of marker to keep (order is kept in the returned data). - If idx has more than one row, output is the mean of the markers over the columns. - Returns - ------- - numpy.array - extracted data - """ - idx = np.array(idx, ndmin=2) - try: - data = self[:, idx[0, :], :] - for i in range(1, idx.shape[0]): - data += self[:, np.array(idx)[i, :], :] - data /= idx.shape[0] - except IndexError: - raise IndexError( - "get_specific_data works only on 3xNxF matrices and idx must be a ixj array" - ) - return data - - # --- Fileio methods (to_*) - - def to_dataframe(self, add_metadata=[]): - """ - Convert a Vectors3d class to a pandas dataframe - - Parameters - ---------- - add_metadata : list - add each metadata specified in this list to the dataframe - - Returns - ------- - pd.DataFrame - """ - cols = {} - for imeta in add_metadata: - ivalue = getattr(self, imeta) - if isinstance(ivalue, dict): - cols.update({key: value for key, value in ivalue.items()}) - else: - cols.update({imeta: ivalue}) - return pd.DataFrame(self.to_2d(), columns=self.get_2d_labels()).assign(**cols) - - def to_csv(self, file_name, header=False): - """ - Write a csv file from a FrameDependentNpArray - - Parameters - ---------- - file_name : str, Path - path of the file to write - header : bool, optional - Write header with labels (default False) - """ - file_name = self.check_parent_dir(file_name) - - # Get the 2d style labels - if header: - header = self.get_2d_labels() - - # Write into the csv file - pd.DataFrame(self.to_2d()).to_csv(file_name, index=False, header=header) - - def to_mat(self, file_name, metadata=False): - """ - Write a Matlab's mat file from a FrameDependentNpArray - - Parameters - ---------- - file_name : str, Path - path of the file to write - metadata : bool, optional - Write data with metadata (default False) - - Returns - ------- - - """ - file_name = self.check_parent_dir(file_name) - mat_dict = {} - if metadata: - mat_dict.update( - { - "get_first_frame": self.get_first_frame, - "get_last_frame": self.get_last_frame, - "get_time_frames": self.get_time_frames, - "get_rate": self.get_rate, - "get_labels": self.get_labels, - "get_unit": self.get_unit, - "get_nan_idx": self.get_nan_idx, - } - ) - mat_dict.update({"data": self}) - savemat(file_name, mat_dict) - - def to_numpy(self): - """ - Return a numpy array - - - Returns - ------- - - np.array - """ - return np.array(self) - - # --- Plot method - - def plot(self, x=None, ax=None, fmt="", lw=1, label=None, alpha=1): - """ - Plot a pyomeca vector3d (Markers3d, Analogs3d, etc.) - - Parameters - ---------- - x : np.ndarray, optional - data to plot on x axis - ax : matplotlib axe, optional - axis on which the data will be ploted - fmt : str - color of the line - lw : int - line width of the line - label : str - label associated with the data (useful to plot legend) - alpha : int, float - alpha - """ - if not ax: - _, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 4)) - - for i in range(self.shape[0]): - data_to_plot = np.squeeze(self[i, :, :]).transpose() - if np.any(x): - ax.plot(x, data_to_plot, fmt, lw=lw, label=label, alpha=alpha) - elif ( - self.get_time_frames is None - or self.get_time_frames.shape[0] != self.shape[2] - ): - ax.plot(data_to_plot, fmt, lw=lw, label=label, alpha=alpha) - else: - ax.plot( - self.get_time_frames, - data_to_plot, - fmt, - lw=lw, - label=label, - alpha=alpha, - ) - return ax - - # --- Signal processing methods - - def matmul(self, other): - """ - Matrix product of two arrays. - - Parameters - ---------- - other : np.ndarray - Second matrix to multiply - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast(np.matmul(self, other)) - - def abs(self): - """ - Get absolute values - - Returns - ------- - FrameDependentNpArray - """ - return np.abs(self) - - def square(self): - """ - Get square of values - - Returns - ------- - FrameDependentNpArray - """ - return np.square(self) - - def sqrt(self): - """ - Get square root of values - - Returns - ------- - FrameDependentNpArray - """ - return np.sqrt(self) - - def mean(self, *args, axis=2, **kwargs): - """ - Get mean values (default over time) - - Returns - ------- - FrameDependentNpArray - """ - return super().mean(*args, axis=axis, keepdims=True, **kwargs) - - def nanmean(self, *args, axis=2, **kwargs): - """ - Get mean values ignoring NaNs (default over time) - - Returns - ------- - FrameDependentNpArray - """ - return np.nanmean(self, *args, axis=axis, keepdims=True, **kwargs) - - def rms(self, axis=2): - """ - Get root-mean-square values - - Returns - ------- - FrameDependentNpArray - """ - return self.square().nanmean().sqrt() - - def center(self, mu=None, axis=-1): - """ - Center a signal (i.e., subtract the mean) - - Parameters - ---------- - mu : np.ndarray, float, int - mean of the signal to subtract, optional - axis : int, optional - axis along which the means are computed. The default is to compute - the mean on the last axis. - Returns - ------- - FrameDependentNpArray - """ - if not np.any(mu): - mu = np.nanmean(self, axis=axis) - if self.ndim > mu.ndim: - # add one dimension if the input is a 3d matrix - mu = np.expand_dims(mu, axis=-1) - return self - mu - - def max(self, *args, axis=2, **kwargs): - """ - Get maximal value (default over time) - - Returns - ------- - float - """ - return super().max(*args, axis=axis, **kwargs) - - def normalization(self, ref=None, scale=100): - """ - Normalize a signal against `ref` (x's max if empty) on a scale of `scale` - - Parameters - ---------- - ref : np.ndarray - reference value - scale - Scale on which to express x (100 by default) - - Returns - ------- - FrameDependentNpArray - """ - if not np.any(ref): - ref = np.nanmax(self, axis=-1) - # add one dimension - ref = np.expand_dims(ref, axis=-1) - return self / (ref / scale) - - def time_normalization(self, time_vector=np.linspace(0, 100, 101), axis=-1): - """ - Time normalization used for temporal alignment of data - - Parameters - ---------- - time_vector : np.ndarray - desired time vector (0 to 100 by step of 1 by default) - axis : int - specifies the axis along which to interpolate. Interpolation defaults to the last axis (over frames) - - Returns - ------- - FrameDependentNpArray - """ - original_time_vector = np.linspace( - time_vector[0], time_vector[-1], self.shape[axis] - ) - f = interp1d(original_time_vector, self, axis=axis) - return self.dynamic_child_cast(f(time_vector)) - - def fill_values(self, axis=-1): - """ - Fill values. Warning: this function can be used only for very small gaps in your data. - - Parameters - ---------- - axis : int - specifies the axis along which to interpolate. Interpolation defaults to the last axis (over frames) - - Returns - ------- - FrameDependentNpArray - """ - - def fct(m): - """Simple function to interpolate along an axis""" - nans, y = np.isnan(m), lambda z: z.nonzero()[0] - m[nans] = np.interp(y(nans), y(~nans), m[~nans]) - return m - - if np.any(self.get_nan_idx): - # do not take nan dimensions - index = np.ones(self.shape[1], dtype=bool) - index[self.get_nan_idx] = False - x = self[:, index, :].copy() - - out = np.apply_along_axis(fct, axis=axis, arr=x) - # reinsert nan dimensions - for i in self.get_nan_idx: - out = np.insert(out, i, np.nan, axis=1) - else: - x = self.copy() - out = np.apply_along_axis(fct, axis=axis, arr=x) - return self.dynamic_child_cast(out) - - def check_for_nans(self, threshold_channel=10, threshold_consecutive=5): - """ - 1. Check if there is less than `threshold_channel`% of nans on each channel - 2. Check if there is not more than `threshold_consecutive`% of the rate of consecutive nans - - Parameters - ---------- - threshold_channel : int - Threshold of tolerated nans on each channel - threshold_consecutive : int - Threshold of tolerated consecutive nans on each channel - """ - # check if there is nans - if self.get_nan_idx is not None: - nans = np.isnan( - self[:, np.setdiff1d(np.arange(self.shape[1]), self.get_nan_idx), :] - ) - else: - nans = np.isnan(self) - if nans.any(): - # check if there is less than `threshold_channel`% of nans on each channel - percentage = (nans.sum(axis=-1) / self.shape[-1] * 100).ravel() - above = np.argwhere(percentage > threshold_channel) - if above.any(): - for iabove in above: - if iabove not in self.get_nan_idx: - raise ValueError( - f"There is more than {threshold_channel}% ({percentage[iabove]}) " - f"NaNs on the channel ({iabove})" - ) - - # check if there is not more than `threshold_consecutive`% of the rate of consecutive nans - def max_consecutive_nans(a): - mask = np.concatenate(([False], np.isnan(a), [False])) - if ~mask.any(): - return 0 - else: - idx = np.nonzero(mask[1:] != mask[:-1])[0] - return (idx[1::2] - idx[::2]).max() - - consecutive_nans = np.apply_along_axis( - max_consecutive_nans, axis=-1, arr=self - ).ravel() - above = np.argwhere( - consecutive_nans > self.get_rate / threshold_consecutive - ) - percentage = (consecutive_nans / self.shape[-1] * 100).ravel() - if above.any(): - for iabove in above: - if iabove not in self.get_nan_idx: - raise ValueError( - f"There is more than {threshold_consecutive}% ({percentage[iabove]}) " - f"consecutive NaNs on the channel ({iabove})" - ) - return True - else: - return False - - def moving_rms(self, window_size): - """ - Moving root mean square - - Parameters - ---------- - window_size : Union(int, float) - Window size - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast( - np.sqrt(filtfilt(np.ones(window_size) / window_size, 1, self * self)) - ) - - def moving_average(self, window_size): - """ - Moving average - - Parameters - ---------- - window_size : Union(int, float) - Window size - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast( - filtfilt(np.ones(window_size) / window_size, 1, self) - ) - - def moving_median(self, window_size): - """ - Moving median (has a sharper response to abrupt changes than the moving average) - - Parameters - ---------- - window_size : Union(int, float) - Window size (use around [3, 11]) - - Returns - ------- - FrameDependentNpArray - """ - if window_size % 2 == 0: - raise ValueError( - f"window_size should be odd. Add or subtract 1. You provided {window_size}" - ) - if self.ndim == 3: - window_size = [1, 1, window_size] - elif self.ndim == 2: - window_size = [1, window_size] - elif self.ndim == 1: - pass - else: - raise ValueError( - f"dim should be 1, 2 or 3. You provided an array with {self.ndim} dimensions." - ) - return self.dynamic_child_cast(medfilt(self, window_size)) - - def _base_filter(self, freq, order, cutoff, btype, interp_nans): - """ - Butterworth filter - - Parameters - ---------- - freq : Union(int, float) - Sample frequency - order : Int - Order of the filter - cutoff : Int - Cut-off frequency - interp_nans : bool - As this function does not work with nans, check if it is safe to interpolate and then interpolate over nans - btype : str - Filter type - - Returns - ------- - - """ - check_for_nans = self.check_for_nans() - - if not check_for_nans: - # if there is no nans - x = self.dynamic_child_cast(self) - elif interp_nans and check_for_nans: - # if there is some nans and it is safe to interpolate - x = self.dynamic_child_cast(self.fill_values()) - else: - # there is nans and we don't want to interpolate - raise ValueError( - "filters do not work well with nans. Try interp_nans=True flag" - ) - - nyquist = freq / 2 - corrected_freq = np.array(cutoff) / nyquist - b, a = butter(N=order, Wn=corrected_freq, btype=btype) - return filtfilt(b, a, x) - - def low_pass(self, freq, order, cutoff, interp_nans=False): - """ - Low-pass Butterworth filter - - Parameters - ---------- - freq : Union(int, float) - Sample frequency - order : Int - Order of the filter - cutoff : Int - Cut-off frequency - interp_nans : bool - As this function does not work with nans, check if it is safe to interpolate and then interpolate over nans - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast( - self._base_filter(freq, order, cutoff, "low", interp_nans) - ) - - def band_pass(self, freq, order, cutoff, interp_nans=False): - """ - Band-pass Butterworth filter - - Parameters - ---------- - freq : Union(int, float) - Sample frequency - order : Int - Order of the filter - cutoff : List-like - Cut-off frequencies ([lower, upper]) - interp_nans : bool - As this function does not work with nans, check if it is safe to interpolate and then interpolate over nans - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast( - self._base_filter(freq, order, cutoff, "bandpass", interp_nans) - ) - - def band_stop(self, freq, order, cutoff, interp_nans=False): - """ - Band-stop Butterworth filter - - Parameters - ---------- - freq : Union(int, float) - Sample frequency - order : Int - Order of the filter - cutoff : List-like - Cut-off frequencies ([lower, upper]) - interp_nans : bool - As this function does not work with nans, check if it is safe to interpolate and then interpolate over nans - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast( - self._base_filter(freq, order, cutoff, "bandstop", interp_nans) - ) - - def high_pass(self, freq, order, cutoff, interp_nans=False): - """ - Band-stop Butterworth filter - - Parameters - ---------- - freq : Union(int, float) - Sample frequency - order : Int - Order of the filter - cutoff : List-like - Cut-off frequencies ([lower, upper]) - interp_nans : bool - As this function does not work with nans, check if it is safe to interpolate and then interpolate over nans - - Returns - ------- - FrameDependentNpArray - """ - return self.dynamic_child_cast( - self._base_filter(freq, order, cutoff, "high", interp_nans) - ) - - def fft(self, freq, only_positive=True, axis=-1): - """ - Performs a discrete Fourier Transform and return amplitudes and frequencies - - Parameters - ---------- - freq : Union(int, float) - Sample frequency - only_positive : bool - Returns only the positives frequencies if true (True by default) - axis : int - specifies the axis along which to performs the FFT. Performs defaults to the last axis (over frames) - - Returns - ------- - amp (numpy.ndarray) and freqs (numpy.ndarray) - """ - n = self.shape[axis] - yfft = fftpack.fft(self, n) - freqs = fftpack.fftfreq(n, 1.0 / freq) - - if only_positive: - amp = 2 * np.abs(yfft) / n - amp = amp[..., : int(np.floor(n / 2))] - freqs = freqs[: int(np.floor(n / 2))] - else: - amp = np.abs(yfft) / n - return amp, freqs - - def norm(self, axis=(0, 1)): - """ - Compute the matrix norm. Same as np.sqrt(np.sum(np.power(x, 2), axis=0)) - - Parameters - ---------- - axis : int, tuple - specifies the axis along which to compute the norm - Returns - ------- - FrameDependentNpArray - """ - return np.linalg.norm(self, axis=axis) - - def detect_onset(self, threshold=0, above=1, below=0, threshold2=None, above2=1): - """ - Detects onset in vector data. Inspired by Marcos Duarte's works. - - Parameters - ---------- - threshold : double, optional - minimum amplitude to detect - above : double, optional - minimum sample of continuous samples above `threshold` to detect - below : double, optional - minimum sample of continuous samples below `threshold` to ignore - threshold2 : double, None, optional - minimum amplitude of `above2` values in `x` to detect. - above2 - minimum sample of continuous samples above `threshold2` to detect - - Returns - ------- - idx : np.ndarray - onset events - """ - if self.ndim != 3: - raise ValueError( - f"detect_onset works only for vector (ndim == 3). Your data have {self.ndim} dimensions." - ) - if self.shape[0] != 1 or self.shape[1] != 1: - raise ValueError( - f"detect_onset works on a single time-dependent value for instance: self[i, j, :]." - ) - self[0, 0, np.squeeze(np.isnan(self))] = -np.inf - idx = np.argwhere(np.squeeze(self >= threshold)).ravel() - - if np.any(idx): - # initial & final indexes of almost continuous data - idx = np.vstack( - ( - idx[np.diff(np.hstack((-np.inf, idx))) > below + 1], - idx[np.diff(np.hstack((idx, np.inf))) > below + 1], - ) - ).T - # indexes of almost continuous data longer or equal to `above` - idx = idx[idx[:, 1] - idx[:, 0] >= above - 1, :] - - if np.any(idx) and threshold2: - # minimum amplitude of above2 values in x - ic = np.ones(idx.shape[0], dtype=bool) - for irow in range(idx.shape[0]): - if ( - np.count_nonzero( - self[0, 0, idx[irow, 0] : idx[irow, 1] + 1] >= threshold2 - ) - < above2 - ): - ic[irow] = False - idx = idx[ic, :] - - if not np.any(idx): - idx = np.array([]) - return idx - - def detect_outliers(self, onset_idx=None, threshold=3): - """ - Detects data that is `threshold` times the standard deviation calculated on the `onset_idx` - - Parameters - ---------- - onset_idx : numpy.ndarray - Array of onset (first column) and offset (second column). You can use detect_onset to have such a table - threshold : int - Multiple of standard deviation from which data is considered outlier - - Returns - ------- - numpy masked array - """ - if np.any(onset_idx): - mask = np.zeros(self.shape, dtype="bool") - for (inf, sup) in onset_idx: - mask[inf:sup] = 1 - sigma = np.nanstd(self[mask]) - mu = np.nanmean(self[mask]) - else: - sigma = np.nanstd(self) - mu = np.nanmean(self) - return np.ma.masked_where(np.abs(self) > mu + (threshold * sigma), self) - - def derivative(self, window=1): - """ - Performs a derivative of the data, assuming the get_time_frames variable has the same length as the data, - otherwise it returns an error - - Parameters - ---------- - window : int - Number of frame before and after to use. This amount of frames is therefore lost at begining and end of - the data - - Returns - ------- - numpy array - """ - deriv = self.dynamic_child_cast(np.ndarray(self.shape)) - deriv[:, :, 0:window] = np.nan - deriv[:, :, -window:] = np.nan - deriv[:, :, window:-window] = ( - self[:, :, 2 * window :] - self[:, :, 0 : -2 * window] - ) / (self.get_time_frames[2 * window :] - self.get_time_frames[0 : -2 * window]) - return deriv - - -class FrameDependentNpArrayCollection(list): - """ - Collection of time frame array - """ - - # --- Get metadata methods - - def get_frame(self, f): - """ - Get fth frame of the collection - Parameters - ---------- - f : int - Frame to get - Returns - ------- - Collection of frame f - """ - coll = FrameDependentNpArrayCollection() - for element in self: - coll.append(element.get_frame(f)) - return coll - - def get_num_segments(self): - """ - Get the number of segments in the collection - Returns - ------- - n : int - Number of segments in the collection - """ - return len(self) - - def get_num_frames(self): - """ - - Returns - ------- - The number of frames - """ - if len(self) > 0: - if len(self[0].shape) == 2: - return 1 - else: - return self[0].shape[ - 2 - ] # Assume all meshes has the same number of frame, return the first one - else: - return -1 diff --git a/pyomeca/generalized_coordinates.py b/pyomeca/generalized_coordinates.py deleted file mode 100644 index 6e74b63..0000000 --- a/pyomeca/generalized_coordinates.py +++ /dev/null @@ -1,28 +0,0 @@ -import numpy as np - -from pyomeca import FrameDependentNpArray - - -class GeneralizedCoordinate(FrameDependentNpArray): - def __new__(cls, q=np.ndarray((0, 1, 0)), *args, **kwargs): - """ - Parameters - ---------- - data : np.ndarray - nQxNxF matrix of marker positions - """ - - # Reshape if the user sent a 2d instead of 3d shape - if len(q.shape) == 2: - q = np.reshape(q, (q.shape[0], 1, q.shape[1])) - - if q.shape[1] != 1: - raise IndexError("Generalized coordinates cannot have multiple columns") - - return super(GeneralizedCoordinate, cls).__new__(cls, array=q, *args, **kwargs) - - def __array_finalize__(self, obj): - super().__array_finalize__(obj) - # Allow slicing - if obj is None or not isinstance(obj, GeneralizedCoordinate): - return diff --git a/pyomeca/io/read.py b/pyomeca/io/read.py new file mode 100644 index 0000000..08bf23f --- /dev/null +++ b/pyomeca/io/read.py @@ -0,0 +1,155 @@ +from pathlib import Path +from typing import Callable, List, Optional, Union + +import ezc3d +import numpy as np +import pandas as pd +import xarray as xr + +from pyomeca.io.utils import col_spliter, find_end_header_in_opensim_file + + +def read_c3d( + caller: Callable, + filename: Union[str, Path], + usecols: Optional[List[Union[str, int]]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + attrs: Optional[dict] = None, +) -> xr.DataArray: + group = "ANALOG" if caller.__name__ == "Analogs" else "POINT" + + reader = ezc3d.c3d(f"{filename}").c3d_swig + columns = [ + col_spliter(label, prefix_delimiter, suffix_delimiter) + for label in reader.parameters() + .group(group) + .parameter("LABELS") + .valuesAsString() + ] + + get_data_function = getattr(reader, f"get_{group.lower()}s") + + if usecols: + if isinstance(usecols[0], str): + idx = [columns.index(channel) for channel in usecols] + elif isinstance(usecols[0], int): + idx = usecols + else: + raise ValueError( + "usecols should be None, list of string or list of int." + f"You provided {type(usecols)}" + ) + data = get_data_function()[:, idx, :] + channels = [columns[i] for i in idx] + else: + data = get_data_function() + channels = columns + + data_by_frame = 1 if group == "POINT" else reader.header().nbAnalogByFrame() + + attrs = attrs if attrs else {} + attrs["first_frame"] = reader.header().firstFrame() * data_by_frame + attrs["last_frame"] = reader.header().lastFrame() * data_by_frame + attrs["rate"] = reader.header().frameRate() * data_by_frame + attrs["units"] = ( + reader.parameters().group(group).parameter("UNITS").valuesAsString()[0] + ) + + time = np.arange( + start=0, stop=data.shape[-1] / attrs["rate"], step=1 / attrs["rate"] + ) + return caller( + data[0, ...] if group == "ANALOG" else data, channels, time, attrs=attrs + ) + + +def read_csv_or_excel( + caller: Callable, + extension: str, + filename: Union[str, Path], + usecols: Optional[List[Union[str, int]]] = None, + header: Optional[int] = None, + first_row: int = 0, + first_column: Optional[Union[str, int]] = None, + time_column: Optional[Union[str, int]] = None, + trailing_columns: Optional[Union[str, int]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + skip_rows: Optional[List[int]] = None, + pandas_kwargs: Optional[dict] = None, + attrs: Optional[dict] = None, + sheet_name: Union[int, str] = 0, +): + if skip_rows is None: + skip_rows = np.arange(header + 1, first_row) if header else np.arange(first_row) + + if pandas_kwargs is None: + pandas_kwargs = {} + + if extension == "csv": + data = pd.read_csv(filename, header=header, skiprows=skip_rows, **pandas_kwargs) + else: + data = pd.read_excel( + filename, + sheet_name=sheet_name, + header=header, + skiprows=skip_rows, + **pandas_kwargs, + ) + + if time_column is not None: + if isinstance(time_column, int): + time = data.iloc[:, time_column] + data = data.drop(data.columns[time_column], axis=1) + elif isinstance(time_column, str): + time = data[time_column] + data = data.drop(time_column, axis=1) + else: + raise ValueError( + f"time_column should be str or int. It is {type(time_column)}" + ) + else: + time = None + + if first_column: + data = data.drop(data.columns[:first_column], axis=1) + + if trailing_columns: + data = data.drop(data.columns[-trailing_columns:], axis=1) + + channels, idx = caller._get_requested_channels_from_pandas( + data.columns, header, usecols, prefix_delimiter, suffix_delimiter + ) + data = caller._reshape_flat_array(data.values[:, idx] if idx else data.values) + + attrs = attrs if attrs else {} + if "rate" in attrs and time is None: + time = np.arange( + start=0, stop=data.shape[-1] / attrs["rate"], step=1 / attrs["rate"] + ) + return caller(data, channels, time, attrs=attrs) + + +def read_sto_or_mot( + caller: Callable, + filename: Union[str, Path], + end_header: Optional[int] = None, + **kwargs, +): + if end_header is None: + end_header = find_end_header_in_opensim_file(filename) + + data = caller.from_csv( + filename, header=end_header + 1, first_column=0, time_column=0, **kwargs, + ) + data.attrs["rate"] = (1 / (data.time[1] - data.time[0])).round().item() + return data + + +def read_trc(caller: Callable, filename: Union[str, Path], **kwargs): + data = caller.from_csv( + filename, header=3, first_row=6, first_column=1, time_column=1, **kwargs, + ) + data.attrs["rate"] = (1 / (data.time[1] - data.time[0])).round().item() + return data diff --git a/pyomeca/io/utils.py b/pyomeca/io/utils.py new file mode 100644 index 0000000..2644ca9 --- /dev/null +++ b/pyomeca/io/utils.py @@ -0,0 +1,26 @@ +import csv +from typing import Optional + + +def col_spliter(x, p, s): + if p and s: + return x.split(p)[-1].split(s)[0] + if p: + return x.split(p)[-1] + if s: + return x.split(s)[0] + return x + + +def find_end_header_in_opensim_file(filename: str, end_header: Optional[int] = None): + with open(filename, "rt") as f: + reader = csv.reader(f) + for idx, row in enumerate(reader): + if row[0] == "endheader": + end_header = idx + break + if end_header is None: + raise IndexError( + "endheader not detected in your file. Try to specify the `end_header` parameter" + ) + return end_header diff --git a/pyomeca/io/write.py b/pyomeca/io/write.py new file mode 100644 index 0000000..f82f6d0 --- /dev/null +++ b/pyomeca/io/write.py @@ -0,0 +1,27 @@ +from pathlib import Path +from typing import Optional, Union + +import pandas as pd +import xarray as xr +from scipy.io import savemat + + +def to_wide_dataframe(array: xr.DataArray) -> pd.DataFrame: + if array.ndim > 2: + df = array.to_series().unstack().T + df.columns = ["_".join(col).strip() for col in df.columns] + return df + return array.to_series().unstack().T + + +def write_csv( + array: xr.DataArray, filename: Union[str, Path], wide: Optional[bool] = True +): + if wide: + array.meca.to_wide_dataframe().to_csv(filename) + else: + array.to_dataframe().to_csv(filename) + + +def write_matlab(array: xr.DataArray, filename: Union[str, Path]): + savemat(filename, array.to_dict()) diff --git a/pyomeca/markers.py b/pyomeca/markers.py index 40f1eb3..4c1e67b 100644 --- a/pyomeca/markers.py +++ b/pyomeca/markers.py @@ -1,180 +1,513 @@ +from pathlib import Path +from typing import List, Optional, Tuple, Union + import numpy as np +import pandas as pd +import xarray as xr -from pyomeca import FrameDependentNpArray +from pyomeca.io import read, utils +from pyomeca.processing.markers import markers_from_rototrans -class Markers3d(FrameDependentNpArray): - def __new__(cls, data=np.ndarray((3, 0, 0)), names=list(), *args, **kwargs): - """ - Parameters - ---------- - data : np.ndarray - 3xNxF matrix of marker positions - names : list of string - name of the marker that correspond to second dimension of the positions matrix +class Markers: + def __new__( + cls, + data: Optional[Union[np.array, np.ndarray, xr.DataArray]] = None, + channels: Optional[list] = None, + time: Optional[Union[np.array, list, pd.Series]] = None, + **kwargs, + ) -> xr.DataArray: """ - if data.ndim == 2: - data = np.array(Markers3d.from_2d(data)) - - if data.ndim == 3: - s = data.shape - if s[0] == 3: - pos = np.ones((4, s[1], s[2])) - pos[0:3, :, :] = data - elif s[0] == 4: - pos = data - else: - raise IndexError( - "Vectors3d must have a length of 3 on the first dimension" - ) - else: - raise TypeError("Data must be 2d or 3d matrix") + Markers DataArray with `axis`, `channel` and `time` dimensions used for skin marker positions. + ![markers](/images/objects/markers.svg) - return super(Markers3d, cls).__new__(cls, array=pos, *args, **kwargs) + Arguments: + data: Array to be passed to xarray.DataArray + channels: Channel names + time: Time vector in seconds associated with the `data` parameter + kwargs: Keyword argument(s) to be passed to xarray.DataArray - def __array_finalize__(self, obj): - super().__array_finalize__(obj) - # Allow slicing - if obj is None or not isinstance(obj, Markers3d): - return + Returns: + Markers `xarray.DataArray` with the specified data and coordinates - # --- Get metadata methods + !!! example + To instantiate a `Markers` with 4 channels and 100 frames filled with some random data: - def get_num_markers(self): - """ - Returns - ------- - The number of markers - """ - return self.shape[1] + ```python + import numpy as np + from pyomeca import Markers + + n_axis = 3 + n_channels = 4 + n_frames = 100 + data = np.random.random(size=(n_axis, n_channels, n_frames)) + markers = Markers(data) + ``` + + You can add the channel names: + + ```python + names = ["A", "B", "C", "D"] + markers = Markers(data, channels=names) + ``` + + And an associate time vector: + + ```python + rate = 100 # Hz + time = np.arange(start=0, stop=n_frames / rate, step=1 / rate) + markers = Markers(data, channels=names, time=time) + ``` - def get_2d_labels(self): + !!! note + Calling `Markers()` generate an empty array. """ - Takes a Markers3d style labels and returns 2d style labels - Returns - ------- - 2d style labels + coords = {} + if data is None: + data = np.ndarray((0, 0, 0)) + else: + coords["axis"] = ["x", "y", "z", "ones"] + if data.shape[0] == 3: + data = np.insert(data, obj=3, values=1, axis=0) + if channels: + coords["channel"] = channels + if time is not None: + coords["time"] = time + return xr.DataArray( + data=data, + dims=("axis", "channel", "time"), + coords=coords, + name="markers", + **kwargs, + ) + + @classmethod + def from_random_data( + cls, distribution: str = "normal", size: tuple = (3, 10, 100), **kwargs + ) -> xr.DataArray: """ - return [i + axe for i in self.get_labels for axe in ["_X", "_Y", "_Z"]] + Create random data from a specified distribution (normal by default) using random walk. - # --- Fileio methods (from_*) + Arguments: + distribution: Distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions) + size: Shape of the desired array + kwargs: Keyword argument(s) to be passed to numpy.random.`distribution` - @staticmethod - def from_2d(m): - """ - Takes a tabular matrix and returns a Vectors3d - Parameters - ---------- - m : np.array - A CSV tabular matrix (Fx3*N) - Returns - ------- - Vectors3d of data set + Returns: + Random markers `xarray.DataArray` sampled from a given distribution + + !!! example + To instantiate a `Markers` with some random data sampled from a normal distribution: + + ```python + from pyomeca import Markers + + n_axis = 3 + n_channels = 10 + n_frames = 100 + size = n_axis, n_channels, n_frames + markers = Markers.from_random_data(size=size) + ``` + + You can choose any distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions): + + ```python + markers = Markers.from_random_data(distribution="uniform", size=size, low=1, high=10) + ``` """ - s = m.shape - if s[1] % 3 != 0: - raise IndexError("Number of columns must be divisible by 3") - return Markers3d(np.reshape(m.T, (3, int(s[1] / 3), s[0]), "F")) + return Markers(getattr(np.random, distribution)(size=size, **kwargs).cumsum(-1)) @classmethod - def from_trc(cls, filename): - return cls.from_csv( + def from_csv( + cls, + filename: Union[str, Path], + usecols: Optional[List[Union[str, int]]] = None, + header: Optional[int] = None, + first_row: int = 0, + first_column: Optional[Union[str, int]] = None, + time_column: Optional[Union[str, int]] = None, + trailing_columns: Optional[Union[str, int]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + skip_rows: Optional[List[int]] = None, + pandas_kwargs: Optional[dict] = None, + attrs: Optional[dict] = None, + ) -> xr.DataArray: + """ + Markers DataArray from a csv file. + + Arguments: + filename: Any valid string path + usecols: All elements must either be positional or strings that correspond to column names. + For example, a valid list-like usecols parameter would be [0, 1, 2] or ['foo', 'bar', 'baz']. + header: Row of the header (0-indexed) + first_row: First row of the data (0-indexed) + first_column: First column of the data (0-indexed) + time_column: Location of the time column. If None, indices are associated + trailing_columns: If for some reason the csv reads extra columns, how many should be ignored + prefix_delimiter: Delimiter that split each column name by its prefix (we keep only the column name) + suffix_delimiter: Delimiter that split each column name by its suffix (we keep only the column name) + skip_rows: Line numbers to skip (0-indexed) + pandas_kwargs: Keyword arguments to be passed to `pandas.read_csv` + attrs: attrs to be passed to `xr.DataArray`. If attrs['rate'] is provided, compute the time accordingly + + Returns: + Markers `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this csv file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers.csv), + type: + + ```python + from pyomeca import Markers + + data_path = "./tests/data/markers.csv" + markers = Markers.from_csv(data_path, header=2, first_row=5, first_column=2) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: + + ```python + channels = ["Daphnee:ASISr", "Daphnee:ASISl", "Daphnee:PSISr"] + markers = Markers.from_csv( + data_path, header=2, first_row=5, first_column=2, usecols=channels + ) + ``` + + Or by position: + + ```python + channels = [5, 6, 7] + markers = Markers.from_csv( + data_path, header=2, first_row=5, first_column=2, usecols=channels + ) + ``` + + Sometimes the channel name is delimited by a suffix or prefix. + To access the prefix, you can specify `prefix_delimiter` and `suffix_delimiter` for the suffix. + For example, if the name is `"Daphnee:ASISr"` and you specify `suffix_delimiter=":"`, you will select "Daphnee". + Similarly, if you specify `prefix_delimiter=":": + + ```python + channels = ["ASISr", "ASISl", "PSISr"] + markers = Markers.from_csv( + data_path, + header=2, + first_row=5, + first_column=2, + usecols=channels, + prefix_delimiter=":", + ) + ``` + + It is also possible to specify a column containing the time vector: + + ```python + markers = Markers.from_csv( + data_path, header=2, first_row=5, first_column=1, time_column=0 + ) + ``` + """ + return read.read_csv_or_excel( + cls, + "csv", filename, - header=3, - first_row=6, - first_column=2, - time_column=1, - delimiter="\t", - last_column_to_remove=1, + usecols, + header, + first_row, + first_column, + time_column, + trailing_columns, + prefix_delimiter, + suffix_delimiter, + skip_rows, + pandas_kwargs, + attrs, ) - # --- Fileio methods (to_*) + @classmethod + def from_excel( + cls, + filename: Union[str, Path], + sheet_name: Union[str, int] = 0, + usecols: Optional[List[Union[str, int]]] = None, + header: Optional[int] = None, + first_row: int = 0, + first_column: Optional[Union[str, int]] = None, + time_column: Optional[Union[str, int]] = None, + trailing_columns: Optional[Union[str, int]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + skip_rows: Optional[List[int]] = None, + pandas_kwargs: Optional[dict] = None, + attrs: Optional[dict] = None, + ) -> xr.DataArray: + """ + Markers DataArray from an Excel file. + + Arguments: + filename: Any valid string path + sheet_name: Strings are used for sheet names. Integers are used in zero-indexed sheet positions + usecols: All elements must either be positional or strings that correspond to column names. + For example, a valid list-like usecols parameter would be [0, 1, 2] or ['foo', 'bar', 'baz']. + header: Row of the header (0-indexed) + first_row: First row of the data (0-indexed) + first_column: First column of the data (0-indexed) + time_column: Location of the time column. If None, indices are associated + trailing_columns: If for some reason the csv reads extra columns, how many should be ignored + prefix_delimiter: Delimiter that split each column name by its prefix (we keep only the column name) + suffix_delimiter: Delimiter that split each column name by its suffix (we keep only the column name) + skip_rows: Line numbers to skip (0-indexed) + pandas_kwargs: Keyword arguments to be passed to `pandas.read_excel` + attrs: attrs to be passed to `xr.DataArray`. If attrs['rate'] is provided, compute the time accordingly + + Returns: + Markers `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this excel file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers.xlsx), + type: + + ```python + from pyomeca import Markers + + data_path = "./tests/data/markers.xlsx" + markers = Markers.from_excel(data_path, header=2, first_row=5, first_column=2) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: + + ```python + channels = ["boite:gauche_ext"] + markers = Markers.from_excel( + data_path, header=2, first_row=5, first_column=2, usecols=channels + ) + ``` - def to_2d(self): + Or by position: + + ```python + channels = [1] + markers = Markers.from_excel( + data_path, header=2, first_row=5, first_column=2, usecols=channels + ) + ``` + + Sometimes the channel name is delimited by a suffix or prefix. + To access the prefix, you can specify `prefix_delimiter` and `suffix_delimiter` for the suffix. + For example, if the name is `"boite:gauche_ext"` and you specify `suffix_delimiter=":"`, you will select "boite". + Similarly, if you specify `prefix_delimiter=":": + + ```python + channels = ["gauche_ext"] + markers = Markers.from_excel( + data_path, + header=2, + first_row=5, + first_column=2, + usecols=channels, + prefix_delimiter=":", + ) + ``` + + It is also possible to specify a column containing the time vector: + + ```python + markers = Markers.from_excel( + data_path, header=2, first_row=5, first_column=1, time_column=0 + ) + ``` """ - Takes a Markers3d style matrix and returns a tabular matrix - Returns - ------- - Tabular matrix + return read.read_csv_or_excel( + cls, + "excel", + filename, + usecols, + header, + first_row, + first_column, + time_column, + trailing_columns, + prefix_delimiter, + suffix_delimiter, + skip_rows, + pandas_kwargs, + attrs, + sheet_name, + ) + + @classmethod + def from_c3d( + cls, + filename: Union[str, Path], + usecols: Optional[List[Union[str, int]]] = None, + prefix_delimiter: Optional[str] = None, + suffix_delimiter: Optional[str] = None, + attrs: Optional[dict] = None, + ) -> xr.DataArray: """ - return np.reshape( - self[0:3, :, :], (3 * self.get_num_markers(), self.get_num_frames()), "F" - ).T + Markers DataArray from a c3d file. - @staticmethod - def _parse_c3d(c3d, prefix): + Arguments: + filename: Any valid string path + usecols: All elements must either be positional or strings that correspond to column names. + For example, a valid list-like usecols parameter would be [0, 1, 2] or ['foo', 'bar', 'baz']. + prefix_delimiter: Delimiter that split each column name by its prefix (we keep only the column name) + suffix_delimiter: Delimiter that split each column name by its suffix (we keep only the column name) + attrs: attrs to be passed to xr.DataArray + + Returns: + Markers `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this c3d file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers_analogs.c3d), + type: + + ```python + from pyomeca import Markers + + data_path = "./tests/data/markers_analogs.c3d" + markers = Markers.from_c3d(data_path) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in: + + ```python + channels = ["Daphnee:ASISl", "Daphnee:PSISr", "Daphnee:PSISl"] + markers = Markers.from_c3d(data_path, usecols=channels) + ``` + + Sometimes the channel name is delimited by a suffix or prefix. + To access the prefix, you can specify `prefix_delimiter` and `suffix_delimiter` for the suffix. + For example, if the name is `""Daphnee:ASISl"` and you specify `suffix_delimiter=":"`, you will select "Daphnee". + Similarly, if you specify `prefix_delimiter=":": + + ```python + channels = ["ASISl", "PSISr", "PSISl"] + markers = Markers.from_c3d(data_path, prefix_delimiter=":") + ``` """ - Implementation on how to read c3d header and parameter for markers - Parameters - ---------- - c3d : ezc3d - ezc3d class - - prefix : str, optional - Participant's prefix - - Returns - ------- - metadata, channel_names, data + return read.read_c3d( + cls, filename, usecols, prefix_delimiter, suffix_delimiter, attrs + ) + + @classmethod + def from_trc(cls, filename: Union[str, Path], **kwargs) -> xr.DataArray: """ - channel_names = [ - i.split(prefix)[-1] - for i in c3d.parameters() - .group("POINT") - .parameter("LABELS") - .valuesAsString() - ] - metadata = { - "get_num_markers": c3d.header().nb3dPoints(), - "get_num_frames": c3d.header().nbFrames(), - "get_first_frame": c3d.header().firstFrame(), - "get_last_frame": c3d.header().lastFrame(), - "get_time_frames": None, - "get_rate": c3d.header().frameRate(), - "get_unit": c3d.parameters() - .group("POINT") - .parameter("UNITS") - .valuesAsString()[0], - } - data = c3d.get_points() - - return data, channel_names, metadata - - # --- Linear algebra methods - - def rotate(self, rt): + Markers DataArray from a trc file. + + Arguments: + filename: Any valid string path + kwargs: Keyword arguments to be passed to `from_csv` + + Returns: + Markers `xarray.DataArray` with the specified data and coordinates + + !!! example + To read [this trc file](https://github.com/pyomeca/pyomeca/blob/master/tests/data/markers.trc), + type: + + ```python + from pyomeca import Markers + + data_path = "./tests/data/markers.trc" + markers = Markers.from_trc(data_path) + ``` + + If you know the channel names, you can retrieve only the ones you are interested in by specifying strings: + + ```python + channels = ["STER", "STERl"] + markers = Markers.from_trc(data_path, usecols=channels) + ``` + + Or by position: + + ```python + channels = [3, 4] + markers = Markers.from_trc(data_path, usecols=channels) + ``` """ - Parameters - ---------- - rt : RotoTrans - Rototranslation matrix to rotate about - - Returns - ------- - A new Vectors3d rotated + return read.read_trc(cls, filename, **kwargs) + + @classmethod + def from_rototrans(cls, markers: xr.DataArray, rt: xr.DataArray) -> xr.DataArray: """ - s_m = self.shape - s_rt = rt.shape - - if len(s_rt) == 2 and len(s_m) == 2: - m2 = rt.dot(self) - elif len(s_rt) == 2 and len(s_m) == 3: - m2 = np.einsum("ij,jkl->ikl", rt, self) - elif len(s_rt) == 3 and len(s_m) == 3: - m2 = np.einsum("ijk,jlk->ilk", rt, self) - else: - raise ValueError("Size of RT and M must match") + Rotates markers data from a rototrans matrix. - return Markers3d(data=m2) + Arguments: + markers: markers array to rotate + rt: Rototrans to rotate about - def norm(self): - """ - Compute the Euclidean norm of vectors Returns: - ------- - Norm + A rotated `xarray.DataArray` + + !!! example + To rotate a random markers set from random angles: + + ```python + from pyomeca import Angles, Rototrans, Markers + + n_frames = 100 + n_markers = 10 + + angles = Angles.from_random_data(size=(3, 1, n_frames)) + rt = Rototrans.from_euler_angles(angles, "xyz") + markers = Markers.from_random_data(size=(3, n_markers, n_frames)) + + rotated_markers = Markers.from_rototrans(markers, rt) + ``` """ - square = self[0:3, :, :] ** 2 - sum_square = np.sum(square, axis=0, keepdims=True) - norm = np.sqrt(sum_square) - return norm # TODO: clean this + return markers_from_rototrans(markers, rt) + + @staticmethod + def _reshape_flat_array(array: Union[np.array, np.ndarray]) -> xr.DataArray: + if array.shape[1] % 3 != 0: + raise IndexError( + "Array second dimension should be divisible by 3. " + f"You provided an array with this shape {array.shape}" + ) + return array.T.reshape((3, int(array.shape[1] / 3), array.shape[0]), order="F") + + @staticmethod + def _get_requested_channels_from_pandas( + columns, header, usecols, prefix_delimiter: str, suffix_delimiter: str + ) -> Tuple[Optional[list], Optional[list]]: + if usecols: + idx, channels = [], [] + if isinstance(usecols[0], int): + for i in usecols: + real_idx = i * 3 + idx.extend([real_idx, real_idx + 1, real_idx + 2]) + channels.append( + utils.col_spliter( + columns[real_idx], prefix_delimiter, suffix_delimiter + ) + ) + elif isinstance(usecols[0], str): + columns_split = [ + utils.col_spliter(col, prefix_delimiter, suffix_delimiter) + for col in columns + ] + for col in usecols: + i = columns_split.index(col) + idx.extend([i, i + 1, i + 2]) + channels.append(col) + else: + raise ValueError( + "usecols should be None, list of string or list of int." + f"You provided {type(usecols)}" + ) + return channels, idx + + if header is None: + return None, None + + channels = [ + utils.col_spliter(col, prefix_delimiter, suffix_delimiter) + for col in columns + if "Unnamed" not in col + ] + return channels, None diff --git a/pyomeca/processing/angles.py b/pyomeca/processing/angles.py new file mode 100644 index 0000000..4b08b77 --- /dev/null +++ b/pyomeca/processing/angles.py @@ -0,0 +1,68 @@ +from typing import Callable + +import numpy as np +import xarray as xr + + +def angles_from_rototrans( + caller: Callable, rt: xr.DataArray, angle_sequence: str +) -> xr.DataArray: + if angle_sequence == "zyzz": + angles = caller(np.ndarray(shape=(3, 1, rt.time.size))) + else: + angles = caller(np.ndarray(shape=(len(angle_sequence), 1, rt.time.size))) + + if angle_sequence == "x": + angles[0, :, :] = np.arcsin(rt[2, 1, :]) + elif angle_sequence == "y": + angles[0, :, :] = np.arcsin(rt[0, 2, :]) + elif angle_sequence == "z": + angles[0, :, :] = np.arcsin(rt[1, 0, :]) + elif angle_sequence == "xy": + angles[0, :, :] = np.arcsin(rt[2, 1, :]) + angles[1, :, :] = np.arcsin(rt[0, 2, :]) + elif angle_sequence == "xz": + angles[0, :, :] = -np.arcsin(rt[1, 2, :]) + angles[1, :, :] = -np.arcsin(rt[0, 1, :]) + elif angle_sequence == "yx": + angles[0, :, :] = -np.arcsin(rt[2, 0, :]) + angles[1, :, :] = -np.arcsin(rt[1, 2, :]) + elif angle_sequence == "yz": + angles[0, :, :] = np.arcsin(rt[0, 2, :]) + angles[1, :, :] = np.arcsin(rt[1, 0, :]) + elif angle_sequence == "zx": + angles[0, :, :] = np.arcsin(rt[1, 0, :]) + angles[1, :, :] = np.arcsin(rt[2, 1, :]) + elif angle_sequence == "zy": + angles[0, :, :] = -np.arcsin(rt[0, 1, :]) + angles[1, :, :] = -np.arcsin(rt[2, 0, :]) + elif angle_sequence == "xyz": + angles[0, :, :] = np.arctan2(-rt[1, 2, :], rt[2, 2, :]) + angles[1, :, :] = np.arcsin(rt[0, 2, :]) + angles[2, :, :] = np.arctan2(-rt[0, 1, :], rt[0, 0, :]) + elif angle_sequence == "xzy": + angles[0, :, :] = np.arctan2(rt[2, 1, :], rt[1, 1, :]) + angles[2, :, :] = np.arctan2(rt[0, 2, :], rt[0, 0, :]) + angles[1, :, :] = np.arcsin(-rt[0, 1, :]) + elif angle_sequence == "yzx": + angles[2, :, :] = np.arctan2(-rt[1, 2, :], rt[1, 1, :]) + angles[0, :, :] = np.arctan2(-rt[2, 0, :], rt[0, 0, :]) + angles[1, :, :] = np.arcsin(rt[1, 0, :]) + elif angle_sequence == "zxy": + angles[1, :, :] = np.arcsin(rt[2, 1, :]) + angles[2, :, :] = np.arctan2(-rt[2, 0, :], rt[2, 2, :]) + angles[0, :, :] = np.arctan2(-rt[0, 1, :], rt[1, 1, :]) + elif angle_sequence in ["zyz", "zyzz"]: + angles[0, :, :] = np.arctan2(rt[1, 2, :], rt[0, 2, :]) + angles[1, :, :] = np.arccos(rt[2, 2, :]) + angles[2, :, :] = np.arctan2(rt[2, 1, :], -rt[2, 0, :]) + elif angle_sequence == "zyx": + angles[2, :, :] = np.arctan2(rt[2, 1, :], rt[2, 2, :]) + angles[1, :, :] = np.arcsin(-rt[2, 0, :]) + angles[0, :, :] = np.arctan2(rt[1, 0, :], rt[0, 0, :]) + elif angle_sequence == "zxz": + angles[0, :, :] = np.arctan2(rt[0, 2, :], -rt[1, 2, :]) + angles[1, :, :] = np.arccos(rt[2, 2, :]) + angles[2, :, :] = np.arctan2(rt[2, 0, :], rt[2, 1, :]) + + return angles diff --git a/pyomeca/processing/filter.py b/pyomeca/processing/filter.py new file mode 100644 index 0000000..0f3442f --- /dev/null +++ b/pyomeca/processing/filter.py @@ -0,0 +1,61 @@ +from typing import Optional, Union + +import numpy as np +import xarray as xr +from scipy.signal import butter, filtfilt + + +def _base_filter( + array: xr.DataArray, + order: int, + cutoff: Union[list, tuple, np.array], + freq: Optional[Union[int, float]], + btype: str, +) -> xr.DataArray: + if freq is None: + if array.attrs.get("rate"): + freq = array.rate + else: + raise ValueError( + "the `freq` param is optional only if `rate` is available in the attrs dictionnary (array.attrs`)" + ) + nyquist = freq / 2 + corrected_freq = np.array(cutoff) / nyquist + b, a = butter(N=order, Wn=corrected_freq, btype=btype) + return xr.apply_ufunc(filtfilt, b, a, array) + + +def low_pass( + array: xr.DataArray, + order: int, + cutoff: Union[int, float, np.array], + freq: Optional[Union[int, float]] = None, +) -> xr.DataArray: + return _base_filter(array, order, cutoff, freq, btype="low") + + +def high_pass( + array: xr.DataArray, + order: int, + cutoff: Union[int, float, np.array], + freq: Optional[Union[int, float]] = None, +) -> xr.DataArray: + return _base_filter(array, order, cutoff, freq, btype="high") + + +def band_pass( + array: xr.DataArray, + order: int, + cutoff: Union[list, tuple, np.array], + freq: Optional[Union[int, float]] = None, +) -> xr.DataArray: + return _base_filter(array, order, cutoff, freq, btype="bandpass") + + +def band_stop( + array: xr.DataArray, + freq: Optional[Union[int, float]], + order: int, + cutoff: Union[list, tuple, np.array], +) -> xr.DataArray: + return _base_filter(array, freq, order, cutoff, btype="bandstop") diff --git a/pyomeca/processing/interp.py b/pyomeca/processing/interp.py new file mode 100644 index 0000000..568a47c --- /dev/null +++ b/pyomeca/processing/interp.py @@ -0,0 +1,22 @@ +from typing import Union + +import numpy as np +import xarray as xr + + +def time_normalize( + array: xr.DataArray, + time_vector: Union[xr.DataArray, np.array] = None, + n_frames: int = 100, + norm_time: bool = False, +) -> xr.DataArray: + if time_vector is None: + if norm_time: + first_last_time = (0, 99) + array["time"] = np.linspace( + first_last_time[0], first_last_time[1], array["time"].shape[0], + ) + else: + first_last_time = (array.time[0], array.time[-1]) + time_vector = np.linspace(first_last_time[0], first_last_time[1], n_frames) + return array.interp(time=time_vector) diff --git a/pyomeca/processing/markers.py b/pyomeca/processing/markers.py new file mode 100644 index 0000000..5f0e24c --- /dev/null +++ b/pyomeca/processing/markers.py @@ -0,0 +1,17 @@ +import numpy as np +import xarray as xr + + +def markers_from_rototrans(markers: xr.DataArray, rt: xr.DataArray) -> xr.DataArray: + rotated_markers = markers.copy() + + if rt.ndim == 3 and markers.ndim == 3: + rotated_markers.data = np.einsum("ijk,jlk->ilk", rt, markers) + elif rt.ndim == 2 and markers.ndim == 2: + rotated_markers.data = np.dot(rt, markers) + elif rt.ndim == 2 and markers.ndim == 3: + rotated_markers.data = np.einsum("ij,jkl->ikl", rt, markers) + else: + raise ValueError("`rt` and `markers` dimensions do not match.") + + return rotated_markers diff --git a/pyomeca/processing/matrix.py b/pyomeca/processing/matrix.py new file mode 100644 index 0000000..473fcb1 --- /dev/null +++ b/pyomeca/processing/matrix.py @@ -0,0 +1,53 @@ +from typing import Union + +import numpy as np +import xarray as xr + + +def abs_(array: xr.DataArray) -> xr.DataArray: + return np.abs(array) + + +def matmul(array: xr.DataArray, other: xr.DataArray) -> xr.DataArray: + return array @ other + + +def square(array: xr.DataArray, **kwargs) -> xr.DataArray: + return np.square(array, **kwargs) + + +def norm(array: xr.DataArray, dim: Union[str, list], ord: int = None) -> xr.DataArray: + return xr.apply_ufunc( + np.linalg.norm, + array.drop_sel(axis="ones") + if hasattr(array, "axis") and "ones" in array.axis + else array, + input_core_dims=[[dim]] if isinstance(dim, str) else dim, + kwargs={"ord": ord, "axis": -1}, + ) + + +def sqrt(array: xr.DataArray, **kwargs) -> xr.DataArray: + return np.sqrt(array, **kwargs) + + +def rms(array: xr.DataArray) -> xr.DataArray: + return array.meca.square().mean().meca.sqrt() + + +def center( + array: xr.DataArray, mu: Union[xr.DataArray, np.array, float, int] = None +) -> xr.DataArray: + if mu is None: + return array - array.mean(dim="time") + return array - mu + + +def normalize( + array: xr.DataArray, + ref: Union[xr.DataArray, np.array, float, int] = None, + scale: Union[int, float] = 100, +) -> xr.DataArray: + if ref is None: + ref = array.max(dim="time") + return array / (ref / scale) diff --git a/pyomeca/processing/misc.py b/pyomeca/processing/misc.py new file mode 100644 index 0000000..8721336 --- /dev/null +++ b/pyomeca/processing/misc.py @@ -0,0 +1,86 @@ +from typing import Union + +import numpy as np +import xarray as xr +from scipy import fftpack + + +def has_correct_name(array: xr.DataArray, name: str): + if array.name != name: + raise ValueError(f"The provided array is not a {name}; It is a {array.name}.") + + +def fft( + array: xr.DataArray, freq: Union[int, float], only_positive=True +) -> xr.DataArray: + n = array.time.shape[0] + yfft = fftpack.fft(array.values, n) + freqs = fftpack.fftfreq(n, 1 / freq) + if only_positive: + amp = 2 * np.abs(yfft) / n + half = int(np.floor(n / 2)) + amp = amp[..., :half] + freqs = freqs[:half] + else: + amp = np.abs(yfft) / n + + coords = {} + if "axis" in array.dims: + coords["axis"] = array.axis + coords["channel"] = array.channel + coords["freq"] = freqs + + return xr.DataArray(data=amp, dims=coords.keys(), coords=coords) + + +def detect_onset( + x, + threshold: Union[float, int], + n_above: int = 1, + n_below: int = 0, + threshold2: int = None, + n_above2: int = 1, +) -> np.array: + if x.ndim != 1: + raise ValueError( + f"detect_onset works only for one-dimensional vector. You have {x.ndim} dimensions." + ) + if isinstance(threshold, xr.DataArray): + threshold = threshold.item() + if isinstance(threshold2, xr.DataArray): + threshold2 = threshold2.item() + + x = np.atleast_1d(x.copy()) + x[np.isnan(x)] = -np.inf + inds = np.nonzero(x >= threshold)[0] + if inds.size: + # initial and final indexes of almost continuous data + inds = np.vstack( + ( + inds[np.diff(np.hstack((-np.inf, inds))) > n_below + 1], + inds[np.diff(np.hstack((inds, np.inf))) > n_below + 1], + ) + ).T + # indexes of almost continuous data longer than or equal to n_above + inds = inds[inds[:, 1] - inds[:, 0] >= n_above - 1, :] + # minimum amplitude of n_above2 values in x to detect + if threshold2 is not None and inds.size: + idel = np.ones(inds.shape[0], dtype=bool) + for i in range(inds.shape[0]): + if ( + np.count_nonzero(x[inds[i, 0] : inds[i, 1] + 1] >= threshold2) + < n_above2 + ): + idel[i] = False + inds = inds[idel, :] + if not inds.size: + inds = np.array([]) + return inds + + +def detect_outliers(array: xr.DataArray, threshold: int = 3) -> xr.DataArray: + mu = array.mean(dim="time") + sigma = array.std(dim="time") + return xr.DataArray( + (array < mu - threshold * sigma) | (array > mu + threshold * sigma) + ) diff --git a/pyomeca/processing/rototrans.py b/pyomeca/processing/rototrans.py new file mode 100644 index 0000000..db6061a --- /dev/null +++ b/pyomeca/processing/rototrans.py @@ -0,0 +1,218 @@ +from typing import Callable, Optional + +import numpy as np +import xarray as xr +from scipy.optimize import least_squares + +from pyomeca import Angles +from pyomeca.processing import misc + + +def rototrans_from_euler_angles( + caller: Callable, + angles: Optional[xr.DataArray] = None, + angle_sequence: Optional[str] = None, + translations: Optional[xr.DataArray] = None, +): + if angles is None: + angles = Angles() + + if translations is None: + translations = Angles() + + if angle_sequence is None: + angle_sequence = "" + + # Convert special zyzz angle sequence to zyz + if angle_sequence == "zyzz": + angles[2, :, :] -= angles[0, :, :] + angle_sequence = "zyz" + + # If the user asked for a pure rotation + if angles.time.size != 0 and translations.time.size == 0: + translations = Angles(np.zeros((3, 1, angles.time.size))) + + # If the user asked for a pure translation + if angles.time.size == 0 and translations.time.size != 0: + angles = Angles(np.zeros((0, 1, translations.time.size))) + + # Sanity checks + if angles.time.size != translations.time.size: + raise IndexError( + "Angles and translations must have the same number of frames. " + f"You have translation = {translations.shape} and angles = {angles.shape}" + ) + if angles.axis.size != len(angle_sequence): + raise IndexError( + "Angles and angles_sequence must be the same size. " + f"You have angles axis = {angles.axis.size} and angle_sequence length = {len(angle_sequence)}" + ) + if angles.time.size == 0: + return caller() + + empty_rt = np.repeat(np.eye(4)[..., np.newaxis], repeats=angles.time.size, axis=2) + rt = empty_rt.copy() + for i in range(angles.axis.size): + a = angles[i, ...] + matrix_to_prod = empty_rt.copy() + if angle_sequence[i] == "x": + # [[1, 0 , 0 ], + # [0, cos(a), -sin(a)], + # [0, sin(a), cos(a)]] + matrix_to_prod[1, 1, :] = np.cos(a) + matrix_to_prod[1, 2, :] = -np.sin(a) + matrix_to_prod[2, 1, :] = np.sin(a) + matrix_to_prod[2, 2, :] = np.cos(a) + elif angle_sequence[i] == "y": + # [[ cos(a), 0, sin(a)], + # [ 0 , 1, 0 ], + # [-sin(a), 0, cos(a)]] + matrix_to_prod[0, 0, :] = np.cos(a) + matrix_to_prod[0, 2, :] = np.sin(a) + matrix_to_prod[2, 0, :] = -np.sin(a) + matrix_to_prod[2, 2, :] = np.cos(a) + elif angle_sequence[i] == "z": + # [[cos(a), -sin(a), 0], + # [sin(a), cos(a), 0], + # [0 , 0 , 1]] + matrix_to_prod[0, 0, :] = np.cos(a) + matrix_to_prod[0, 1, :] = -np.sin(a) + matrix_to_prod[1, 0, :] = np.sin(a) + matrix_to_prod[1, 1, :] = np.cos(a) + else: + raise ValueError( + "angle_sequence must be a permutation of axes (e.g. 'xyz', 'yzx', ...)" + ) + rt = np.einsum("ijk,jlk->ilk", rt, matrix_to_prod) + # Put the translations + rt[:-1, -1:, :] = translations[:3, ...] + return caller(rt) + + +def rototrans_from_markers( + caller: Callable, + origin: xr.DataArray, + axis_1: xr.DataArray, + axis_2: xr.DataArray, + axes_name: str, + axis_to_recalculate: str, +) -> xr.DataArray: + if origin.channel.size != 1: + raise ValueError( + f"`origin` must be only one marker. You have provided {origin.channel.size} markers." + ) + if axis_1.channel.size != 2: + raise ValueError( + f"`axis_1` must be two markers. You have provided {axis_1.channel.size} markers." + ) + if axis_2.channel.size != 2: + raise ValueError( + f"`axis_2` must be two markers. You have provided {axis_2.channel.size} markers." + ) + + # sort the axes name - If we inverse axes_names, inverse axes as well + sorted_axes_name = "".join(sorted(axes_name)) + if axes_name != sorted_axes_name: + axes_name = sorted_axes_name + axis_1, axis_2 = axis_2, axis_1 + + # compute vectors from markers + vector_1 = axis_1[:3, 1, :] - axis_1[:3, 0, :] + vector_2 = axis_2[:3, 1, :] - axis_2[:3, 0, :] + + if origin.time.size != vector_1.time.size or origin.time.size != vector_2.time.size: + raise ValueError("Number of frame(s) for origin and axes must be the same") + + error_msg = "Axes names should be 2 values of `x`, `y` and `z` permutations" + + if axes_name[0] == "x": + x = vector_1 + if axes_name[1] == "y": + y = vector_2 + z = np.cross(x, y, axis=0) + elif axes_name[1] == "z": + z = vector_2 + y = np.cross(z, x, axis=0) + else: + raise ValueError(error_msg) + elif axes_name[0] == "y": + y = vector_1 + if axes_name[1] == "z": + z = vector_2 + x = np.cross(y, z, axis=0) + else: + raise ValueError(error_msg) + else: + raise ValueError(error_msg) + + if axis_to_recalculate == "x": + x = np.cross(y, z, axis=0) + elif axis_to_recalculate == "y": + y = np.cross(z, x, axis=0) + elif axis_to_recalculate == "z": + z = np.cross(x, y, axis=0) + else: + raise ValueError("`axis_to_recalculate must be `x`, `y` or `z`") + + rt = caller(np.zeros((4, 4, origin.time.size))) + rt[:3, 0, :] = x / np.linalg.norm(x, axis=0) + rt[:3, 1, :] = y / np.linalg.norm(y, axis=0) + rt[:3, 2, :] = z / np.linalg.norm(z, axis=0) + rt.meca.translation = origin + return rt + + +def rototrans_from_transposed_rototrans( + caller: Callable, rt: xr.DataArray +) -> xr.DataArray: + rt_t = caller(np.zeros((4, 4, rt.time.size))) + + # the rotation part is just the transposed of the rotation + rt_t.meca.rotation = rt.meca.rotation.transpose("col", "row", "time") + + # the translation part is "- rt_t * translation" + rt_t.meca.translation = np.einsum( + "ijk,jlk->ilk", -rt_t.meca.rotation, rt.meca.translation + ) + + return rt_t + + +def rototrans_from_averaged_rototrans( + caller: Callable, rt: xr.DataArray +) -> xr.DataArray: + # arbitrary angle sequence + seq = "xyz" + + target = rt.mean(dim="time").expand_dims("time", axis=-1) + + angles = Angles(np.ndarray((3, 1, 1))) + + def objective_function(x): + angles[:3, 0, 0] = x + rt = caller.from_euler_angles(angles, seq) + return np.ravel(rt.meca.rotation - target.meca.rotation) + + initia_guess = Angles.from_rototrans(target, seq).squeeze() + angles[:3, 0, 0] = least_squares(objective_function, initia_guess).x + return caller.from_euler_angles(angles, seq, translations=target.meca.translation) + + +def rotation_getter(array: xr.DataArray) -> xr.DataArray: + misc.has_correct_name(array, "rototrans") + return array[:3, :3, :] + + +def rotation_setter(array: xr.DataArray, value: xr.DataArray) -> None: + misc.has_correct_name(array, "rototrans") + array[:3, :3, :] = value[:3, :, :] + + +def translation_getter(array: xr.DataArray) -> xr.DataArray: + misc.has_correct_name(array, "rototrans") + return array[:3, 3:4, :] + + +def translation_setter(array: xr.DataArray, value: xr.DataArray) -> None: + misc.has_correct_name(array, "rototrans") + array[:3, 3:4, :] = value[:3, :, :] diff --git a/pyomeca/rototrans.py b/pyomeca/rototrans.py index 43d5ab4..abb1dcc 100644 --- a/pyomeca/rototrans.py +++ b/pyomeca/rototrans.py @@ -1,481 +1,275 @@ +from typing import Optional, Union + import numpy as np -from scipy.optimize import least_squares +import pandas as pd +import xarray as xr -from pyomeca import FrameDependentNpArray, FrameDependentNpArrayCollection, Markers3d +from pyomeca import Angles +from pyomeca.processing import rototrans -class RotoTrans(FrameDependentNpArray): +class Rototrans: def __new__( cls, - rt=np.eye(4), - angles=FrameDependentNpArray(), - angle_sequence="", - translations=FrameDependentNpArray(), - *args, - **kwargs - ): + data: Optional[Union[np.array, np.ndarray, xr.DataArray]] = None, + time: Optional[Union[np.array, list, pd.Series]] = None, + **kwargs, + ) -> xr.DataArray: """ + Rototrans DataArray with `row`, `col` and `time` dimensions used for rototranslation matrix. + ![rototrans](/images/objects/rototrans.svg) - Parameters - ---------- - rt : FrameDependentNpArray (4x4xF) - Rototranslation matrix sorted in 4x4xF, default is the matrix that don't rotate nor translate the system, is - ineffective if angles is provided - angles : FrameDependentNpArray - Euler angles of the rototranslation, angles parameter is ineffective if angles_sequence if not defined, but - will override rt - angle_sequence : str - Euler sequence of angles; valid values are all permutation of 3 axes (e.g. "xyz", "yzx", ...) - translations : FrameDependentNpArray - First 3 rows of 4th row, translation is ineffective if angles is not provided - """ + Arguments: + data: Array to be passed to xarray.DataArray + time: Time vector in seconds associated with the `data` parameter + kwargs: Keyword argument(s) to be passed to xarray.DataArray - # Determine if we construct RotoTrans from rt or angles/translations - if angle_sequence: - rt = cls.rt_from_euler_angles( - angles=angles, angle_sequence=angle_sequence, translations=translations - ) + Returns: + Rototrans `xarray.DataArray` with the specified data and coordinates - else: - s = rt.shape - if s[0] != 4 or s[1] != 4: - raise IndexError("RotoTrans must by a 4x4xF matrix") - # Make sure last line reads [0, 0, 0, 1] - if len(s) == 2: - rt[3, :] = np.array([0, 0, 0, 1]) - else: - rt[3, 0:3, :] = 0 - rt[3, 3, :] = 1 - - # Finally, we must return the newly created object: - return super(RotoTrans, cls).__new__(cls, array=rt, *args, **kwargs) - - def __array_finalize__(self, obj): - super().__array_finalize__(obj) - # Allow slicing - if obj is None or not isinstance(obj, RotoTrans): - return - - # --- Linear algebra methods - - def get_euler_angles(self, angle_sequence): - """ + !!! example + To instantiate a `Rototrans` 4 by 4 and 100 frames filled with some random data: - Parameters - ---------- - angle_sequence : str - Euler sequence of angles; valid values are all permutation of axes (e.g. "xyz", "yzx", ...) - Returns - ------- - angles : Markers3d - Euler angles associated with RotoTrans - """ - if angle_sequence != "zyzz": - angles = FrameDependentNpArray( - np.ndarray((len(angle_sequence), 1, self.get_num_frames())) - ) - else: - angles = FrameDependentNpArray(np.ndarray((3, 1, self.get_num_frames()))) - - if angle_sequence == "x": - angles[0, :, :] = np.arcsin(self[2, 1, :]) - elif angle_sequence == "y": - angles[0, :, :] = np.arcsin(self[0, 2, :]) - elif angle_sequence == "z": - angles[0, :, :] = np.arcsin(self[1, 0, :]) - elif angle_sequence == "xy": - angles[0, :, :] = np.arcsin(self[2, 1, :]) - angles[1, :, :] = np.arcsin(self[0, 2, :]) - elif angle_sequence == "xz": - angles[0, :, :] = -np.arcsin(self[1, 2, :]) - angles[1, :, :] = -np.arcsin(self[0, 1, :]) - elif angle_sequence == "yx": - angles[0, :, :] = -np.arcsin(self[2, 0, :]) - angles[1, :, :] = -np.arcsin(self[1, 2, :]) - elif angle_sequence == "yz": - angles[0, :, :] = np.arcsin(self[0, 2, :]) - angles[1, :, :] = np.arcsin(self[1, 0, :]) - elif angle_sequence == "zx": - angles[0, :, :] = np.arcsin(self[1, 0, :]) - angles[1, :, :] = np.arcsin(self[2, 1, :]) - elif angle_sequence == "zy": - angles[0, :, :] = -np.arcsin(self[0, 1, :]) - angles[1, :, :] = -np.arcsin(self[2, 0, :]) - elif angle_sequence == "xyz": - angles[0, :, :] = np.arctan2(self[1, 2, :], self[2, 2, :]) - angles[1, :, :] = np.arcsin(self[0, 1, :]) - angles[2, :, :] = np.arctan2(-self[0, 1, :], self[0, 0, :]) - elif angle_sequence == "xzy": - angles[0, :, :] = np.arctan2(self[2, 1, :], self[1, 1, :]) - angles[2, :, :] = np.arctan2(self[0, 2, :], self[0, 0, :]) - angles[1, :, :] = -np.arcsin(self[0, 1, :]) - elif angle_sequence == "xzy": - angles[1, :, :] = -np.arcsin(self[1, 2, :]) - angles[0, :, :] = np.arctan2(self[0, 2, :], self[2, 2, :]) - angles[2, :, :] = np.arctan2(self[1, 0, :], self[1, 1, :]) - elif angle_sequence == "yzx": - angles[2, :, :] = np.arctan2(-self[1, 2, :], self[1, 1, :]) - angles[0, :, :] = np.arctan2(-self[2, 0, :], self[0, 0, :]) - angles[1, :, :] = np.arcsin(self[1, 2, :]) - elif angle_sequence == "zxy": - angles[1, :, :] = np.arcsin(self[2, 1, :]) - angles[2, :, :] = np.arctan2(-self[2, 0, :], self[2, 2, :]) - angles[0, :, :] = np.arctan2(-self[0, 1, :], self[1, 1, :]) - elif angle_sequence == "zyz": - angles[0, :, :] = np.arctan2(self[1, 2, :], self[0, 2, :]) - angles[1, :, :] = np.arccos(self[2, 2, :]) - angles[2, :, :] = np.arctan2(self[2, 1, :], -self[2, 0, :]) - elif angle_sequence == "zxz": - angles[0, :, :] = np.arctan2(self[0, 2, :], -self[1, 2, :]) - angles[1, :, :] = np.arccos(self[2, 2, :]) - angles[2, :, :] = np.arctan2(self[2, 0, :], self[2, 1, :]) - elif angle_sequence == "zyzz": - angles[0, :, :] = np.arctan2(self[1, 2, :], self[0, 2, :]) - angles[1, :, :] = np.arccos(self[2, 2, :]) - angles[2, :, :] = np.arctan2(self[2, 1, :], -self[2, 0, :]) - - return angles - - @staticmethod - def rt_from_euler_angles( - angles=FrameDependentNpArray(), - angle_sequence="", - translations=FrameDependentNpArray(), - ): - """ + ```python + from pyomeca import Rototrans + import numpy as np - Parameters - ---------- - angles : FrameDependentNpArray - Euler angles of the rototranslation - angle_sequence : str - Euler sequence of angles; valid values are all permutation of axes (e.g. "xyz", "yzx", ...) - translations : FrameDependentNpArray - Translation part of the Rototrans matrix - - Returns - ------- - rt : RotoTrans - The rototranslation associated to the input parameters - """ - # Convert special zyzz angle sequence to zyz - if angle_sequence == "zyzz": - angles[2, :, :] -= angles[0, :, :] - angle_sequence = "zyz" - - # If the user asked for a pure rotation - if angles.get_num_frames() != 0 and translations.get_num_frames() == 0: - translations = FrameDependentNpArray( - np.zeros((3, 1, angles.get_num_frames())) - ) + # create random yet homogeneous data + n_frames = 100 + data = Rototrans.from_random_data(size=(4, 4, 100)).data - # If the user asked for a pure translation - if angles.get_num_frames() == 0 and translations.get_num_frames() != 0: - angles = FrameDependentNpArray( - np.zeros((0, 1, translations.get_num_frames())) - ) + rt = Rototrans(data) + ``` - # Sanity checks - if angles.get_num_frames() != translations.get_num_frames(): - raise IndexError( - "angles and translations must have the same number of frames" - ) - if angles.shape[0] is not len(angle_sequence): - raise IndexError("angles and angles_sequence must be the same size") - if angles.get_num_frames() == 0: - return RotoTrans() - - rt_out = np.repeat(np.eye(4)[:, :, np.newaxis], angles.get_num_frames(), axis=2) - try: - for i in range(len(angles)): - a = angles[i, :, :] - matrix_to_prod = np.repeat( - np.eye(4)[:, :, np.newaxis], angles.get_num_frames(), axis=2 - ) - if angle_sequence[i] == "x": - # [[1, 0 , 0 ], - # [0, cos(a), -sin(a)], - # [0, sin(a), cos(a)]] - matrix_to_prod[1, 1, :] = np.cos(a) - matrix_to_prod[1, 2, :] = -np.sin(a) - matrix_to_prod[2, 1, :] = np.sin(a) - matrix_to_prod[2, 2, :] = np.cos(a) - elif angle_sequence[i] == "y": - # [[ cos(a), 0, sin(a)], - # [ 0 , 1, 0 ], - # [-sin(a), 0, cos(a)]] - matrix_to_prod[0, 0, :] = np.cos(a) - matrix_to_prod[0, 2, :] = np.sin(a) - matrix_to_prod[2, 0, :] = -np.sin(a) - matrix_to_prod[2, 2, :] = np.cos(a) - elif angle_sequence[i] == "z": - # [[cos(a), -sin(a), 0], - # [sin(a), cos(a), 0], - # [0 , 0 , 1]] - matrix_to_prod[0, 0, :] = np.cos(a) - matrix_to_prod[0, 1, :] = -np.sin(a) - matrix_to_prod[1, 0, :] = np.sin(a) - matrix_to_prod[1, 1, :] = np.cos(a) - else: - raise ValueError( - "angle_sequence must be a permutation of axes (e.g. " - "xyz" - ", " - "yzx" - ", ...)" - ) - rt_out = np.einsum("ijk,jlk->ilk", rt_out, matrix_to_prod) - except IndexError: - raise ValueError( - "angle_sequence must be a permutation of axes (e.g. " - "xyz" - ", " - "yzx" - ", ...)" - ) + You can an associate time vector: - # Put the translations - rt_out[0:3, 3:4, :] = translations[0:3, :, :] + ```python + rate = 100 # Hz + time = np.arange(start=0, stop=n_frames / rate, step=1 / rate) + rt = Rototrans(data, time=time) + ``` - return RotoTrans(rt_out) - - @staticmethod - def define_axes( - data_set, idx_axis1, idx_axis2, axes_name, axis_to_recalculate, idx_origin - ): - """ - This function creates system of axes from axis1 and axis2 - Parameters - ---------- - data_set : Markers3d - Whole data set - idx_axis1 : list(int) - First column is the beginning of the axis, second is the end. Rows are the markers to be mean - idx_axis2 : list(int) - First column is the beginning of the axis, second is the end. Rows are the markers to be mean - axes_name : str - Name of the axis1 and axis2 in that order ("xy", "yx", "xz", ...) - axis_to_recalculate : str - Which of the 3 axes to recalculate - idx_origin : list(int) - Markers to be mean to define the origin of the system of axes - - Returns - ------- - System of axes + !!! notes + Calling `Rototrans()` generate an empty array. """ - # Extract mean of each required axis indexes - idx_axis1 = np.matrix(idx_axis1) - idx_axis2 = np.matrix(idx_axis2) - - axis1 = data_set.get_specific_data( - idx_axis1[:, 1] - ) - data_set.get_specific_data(idx_axis1[:, 0]) - axis2 = data_set.get_specific_data( - idx_axis2[:, 1] - ) - data_set.get_specific_data(idx_axis2[:, 0]) - origin = data_set.get_specific_data( - np.matrix(idx_origin).reshape((len(idx_origin), 1)) - ) - - axis1 = axis1[0:3, :, :].reshape(3, axis1.shape[2]).T - axis2 = axis2[0:3, :, :].reshape(3, axis2.shape[2]).T - - # If we inverse axes_names, inverse axes as well - axes_name_tp = "".join(sorted(axes_name)) - if axes_name != axes_name_tp: - axis1_copy = axis1 - axis1 = axis2 - axis2 = axis1_copy - axes_name = axes_name_tp - - error_msg = "Axes names should be 2 values of `x`, `y` and `z` permutations" - - if axes_name[0] == "x": - x = axis1 - if axes_name[1] == "y": - y = axis2 - z = np.cross(x, y) - elif axes_name[1] == "z": - z = axis2 - y = np.cross(z, x) - else: - raise ValueError(error_msg) - - elif axes_name[0] == "y": - y = axis1 - if axes_name[1] == "z": - z = axis2 - x = np.cross(y, z) - else: - raise ValueError(error_msg) + coords = {} + if data is None: + data = np.eye(4) else: - raise ValueError(error_msg) - - # Normalize each vector - x = x / np.matrix(np.linalg.norm(x, axis=1)).T - y = y / np.matrix(np.linalg.norm(y, axis=1)).T - z = z / np.matrix(np.linalg.norm(z, axis=1)).T - - # # Recalculate the temporary axis - if axis_to_recalculate == "x": - x = np.cross(y, z) - elif axis_to_recalculate == "y": - y = np.cross(z, x) - elif axis_to_recalculate == "z": - z = np.cross(x, y) - else: - raise ValueError("Axis to recalculate must be `x`, `y` or `z`") + # if we provide data, we copy them to avoid making inplace changes + data = data.copy() - rt = RotoTrans(rt=np.zeros((4, 4, data_set.shape[2]))) - rt[0:3, 0, :] = x.T - rt[0:3, 1, :] = y.T - rt[0:3, 2, :] = z.T - rt.set_translation(origin) - return rt + if data.shape[0] not in (3, 4) or data.shape[0] != data.shape[1]: + raise IndexError( + f"data must have first and second dimensions of length 4, you have: {data.shape}" + ) - def rotation(self): - """ - Returns - ------- - Rotation part of the RotoTrans - """ - return self[0:3, 0:3, :] + if data.ndim == 2: + data = data[..., np.newaxis] - def set_rotation(self, r): - """ - Set rotation part of the RotoTrans - Parameters - ---------- - r : np.array - A 3x3xN rotation matrix - """ - self[0:3, 0:3, :] = r + if time is not None: + coords["time"] = time - def translation(self): - """ - Returns - ------- - Translation part of the RotoTrans - """ - return self[0:3, 3, :] + # Make sure last line reads [0, 0, 0, 1] + data[3, :3, :] = 0 + data[3, 3, :] = 1 - def set_translation(self, t): - """ - Set translation part of the RotoTrans - Parameters - ---------- - t : np.array - A 3x1xN vector - """ - self[0:3, 3, :] = t[0:3, :, :].reshape(3, t.shape[2]) + return xr.DataArray( + data=data, + dims=("row", "col", "time"), + coords=coords, + name="rototrans", + **kwargs, + ) - def transpose(self): + @classmethod + def from_random_data( + cls, distribution: str = "normal", size: tuple = (4, 4, 100), **kwargs + ) -> xr.DataArray: """ + Create random data from a specified distribution (normal by default) using random walk. - Returns - ------- - Rt_t : RotoTrans - Transposed RotoTrans matrix ([R.T -R.T*t],[0 0 0 1]) - """ - # Create a matrix with the transposed rotation part - rt_t = RotoTrans(rt=np.ndarray((4, 4, self.get_num_frames()))) - rt_t[0:3, 0:3, :] = np.transpose(self[0:3, 0:3, :], (1, 0, 2)) + Arguments: + distribution: Distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions) + size: Shape of the desired array + kwargs: Keyword argument(s) to be passed to numpy.random.`distribution` - # Fill the last column and row with 0 and bottom corner with 1 - rt_t[3, 0:3, :] = 0 - rt_t[0:3, 3, :] = 0 - rt_t[3, 3, :] = 1 + Returns: + Random rototrans `xarray.DataArray` sampled from a given distribution - # Transpose the translation part - t = Markers3d(data=np.reshape(self[0:3, 3, :], (3, 1, self.get_num_frames()))) - rt_t[0:3, 3, :] = t.rotate(-rt_t)[0:3, :].reshape((3, self.get_num_frames())) + !!! example + To instantiate a `Rototrans` with some random data sampled from a normal distribution: - # Return transposed matrix - return rt_t + ```python + from pyomeca import Rototrans - def inverse(self): - """ + n_frames = 100 + size = 4, 4, n_frames + rt = Rototrans.from_random_data(size=size) + ``` - Returns - ------- - Inverse of the RotoTrans matrix (which is by definition the transposed matrix) - """ - return self.transpose() + You can choose any distribution available in + [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions): - def mean(self): + ```python + rt = Rototrans.from_random_data(distribution="uniform", size=size, low=1, high=10) + ``` """ + return Rototrans.from_euler_angles( + Angles.from_random_data(distribution, size=(3, 1, size[-1]), **kwargs), + "xyz", + ) - Returns - ------- - Performs an optimization to compute the mean over the frames + @classmethod + def from_euler_angles( + cls, + angles: Optional[xr.DataArray] = None, + angle_sequence: Optional[str] = None, + translations: Optional[xr.DataArray] = None, + ) -> xr.DataArray: """ + Rototrans DataArray from euler angles and specified angle sequence. - # Chose an arbitrary angle sequence to convert into angle during the optimization - seq = "xyz" + Arguments: + angles: Euler angles of the rototranslation matrix + angle_sequence: Euler sequence of angles. Valid values are all permutations of "xyz" + translations: Translation part of the Rototrans matrix - # Compute the element-wise mean for the optimization to target - rt_mean = super(RotoTrans, self).mean() + Returns: + Rototrans `xarray.DataArray` from the specified angles and angles sequence - # Define the objective function - x_tp = FrameDependentNpArray(np.ndarray((3, 1, 1))) + !!! example + To get the rototranslation matrix from random euler angles with a given angle sequence type: - def obj(x): - x_tp[0:3, 0, 0] = x.reshape(-1, 1) - rt = RotoTrans(angles=x_tp, angle_sequence=seq) - return (rt[0:3, 0:3] - rt_mean[0:3, 0:3]).reshape(9) + ```python + from pyomeca import Angles, Rototrans - # Initial guess of the optimization - x0 = np.squeeze(rt_mean.get_euler_angles(seq)) + size = (3, 1, 100) + angles = Angles.from_random_data(size=size) + angles_sequence = "xyz" - # Call the optimizer - x_tp[0:3, 0, 0] = least_squares(obj, x0).x.reshape(-1, 1) - return RotoTrans( - angles=x_tp, angle_sequence=seq, translations=rt_mean[0:3, 3, :] - ) + rt = Rototrans.from_euler_angles(angles=angles, angle_sequence=angles_sequence) + ``` + A translation vector can also be specified: -class RotoTransCollection(FrameDependentNpArrayCollection): - """ - List of RotoTrans - """ + ```python + translation = Angles.from_random_data(size=size) + rt = Rototrans.from_euler_angles( + angles=angles, angle_sequence=angles_sequence, translations=translation + ) + ``` + """ + return rototrans.rototrans_from_euler_angles( + cls, angles, angle_sequence, translations + ) - def get_frame(self, f): + @classmethod + def from_markers( + cls, + origin: xr.DataArray, + axis_1: xr.DataArray, + axis_2: xr.DataArray, + axes_name: str, + axis_to_recalculate: str, + ) -> xr.DataArray: """ - Get fth frame of the collection - Parameters - ---------- - f : int - Frame to get - Returns - ------- - Collection of frame f + Rototrans DataArray from a specified set of markers. + + Arguments: + origin: A marker constructed with `pyomeca.Markers()` corresponding + to the origin in the global reference frame + axis_1: Two markers that describe the first axis. + The first markers being the beginning of the vector and the second being the end. + axis_2: Two markers that describe the second axis. + The first markers being the beginning of the vector and the second being the end. + axes_name: Any combination of `x`, `y` and `z` describing the first and second axes. + axis_to_recalculate: Which of the two axes to recalculate + + Returns: + Rototrans `xarray.DataArray` from the specified angles and angles sequence + + !!! example + To create a system of axes from random markers: + + ```python + from pyomeca import Markers, Rototrans + + markers = Markers.from_random_data() + + rt = Rototrans.from_markers( + origin=markers.isel(channel=[0]), # first marker + axis_1=markers.isel(channel=[0, 1]), # vector from the first and second markers + axis_2=markers.isel(channel=[0, 2]), # vector from the first and third markers + axes_name="xy", # axis_1 is x and axis_2 is y + axis_to_recalculate="y", # we want to recalculate y + ) + ``` """ - coll = RotoTransCollection() - for element in self: - coll.append(element.get_frame(f)) - return coll + return rototrans.rototrans_from_markers( + cls, origin, axis_1, axis_2, axes_name, axis_to_recalculate + ) - def get_rt(self, i): + @classmethod + def from_transposed_rototrans(cls, rt: xr.DataArray) -> xr.DataArray: """ - Get a specific RotoTrans of the collection - Parameters - ---------- - i : int - Index of the RotoTrans in the collection - - Returns - ------- - All frame of RotoTrans of index i + Rototrans DataArray from a tranposed Rototrans. + + Arguments: + rt: Rototrans to transpose + + Returns: + Transposed Rototrans `xarray.DataArray` + + !!! example + ```python + from pyomeca import Rototrans + + rt = Rototrans.from_random_data() + + rt_t = Rototrans.from_transposed_rototrans(rt) + ``` + + !!! notes + The inverse Rototrans is, by definition, equivalent to the tranposed Rototrans. """ - return self[i] + return rototrans.rototrans_from_transposed_rototrans(cls, rt) - def get_num_rt(self): + @classmethod + def from_averaged_rototrans(cls, rt: xr.DataArray) -> xr.DataArray: """ - Get the number of RotoTrans in the collection - Returns - ------- - n : int - Number of RotoTrans in the collection + Rototrans DataArray from an averaged Rototrans. + + Arguments: + rt: Rototrans to average + + Returns: + Averaged Rototrans `xarray.DataArray` + + !!! example + To average a `Rototrans` computed from random angles: + + ```python + import numpy as np + from pyomeca import Angles, Rototrans + + angles = Angles(np.random.rand(3, 1, 100)) + seq = "xyz" + + rt = Rototrans.from_euler_angles(angles, seq) + rt_mean = Rototrans.from_averaged_rototrans(rt) + ``` + + Let's make sure the resulting angles are roughly equivalent + to the averaged angles: + + ```python + angles_mean = Angles.from_rototrans(rt_mean, seq).isel(time=0) + angles_mean_ref = Angles.from_rototrans(rt, seq).mean(dim="time") + + error = (angles_mean - angles_mean_ref).meca.abs().sum() + print(error) + ``` """ - return self.get_num_segments() + return rototrans.rototrans_from_averaged_rototrans(cls, rt) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..156f21a --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,12 @@ +-e . +autoflake +black +bs4 +isort +mkdocs +mkdocs-material +mkdocs-minify-plugin +mkdocstrings +pytest +pytest-cov +requests diff --git a/setup.py b/setup.py index 1433748..a47b288 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,12 @@ setup( name="pyomeca", - description="Pyomeca is a python library allowing to carry out a complete biomechanical analysis; in a simple, logical and concise way", + description="pyomeca is a python library allowing to carry out a complete biomechanical analysis;" + "in a simple, logical and concise way", author="Romain Martinez & Benjamin Michaud", author_email="martinez.staps@gmail.com", url="https://github.com/pyomeca/pyomeca", license="Apache 2.0", packages=["pyomeca"], keywords="pyomeca", - classifiers=[ - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - ], ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/_constants.py b/tests/_constants.py new file mode 100644 index 0000000..7d5aff2 --- /dev/null +++ b/tests/_constants.py @@ -0,0 +1,48 @@ +from pathlib import Path + +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# Path to data +from pyomeca import Analogs, Markers + +mpl.rcParams["axes.spines.right"] = False +mpl.rcParams["axes.spines.top"] = False +plt.style.use("seaborn-ticks") # bmh, ggplot, seaborn-whitegrid + +np.random.seed(42) # to make the random sampling consistent across runs + +if "tests" in f"{Path('.').absolute()}": + DATA_FOLDER = Path("data") +else: + DATA_FOLDER = Path("tests") / "data" + +MARKERS_CSV = DATA_FOLDER / "markers.csv" +MARKERS_ANALOGS_C3D = DATA_FOLDER / "markers_analogs.c3d" +ANALOGS_CSV = DATA_FOLDER / "analogs.csv" +MARKERS_CSV_WITHOUT_HEADER = DATA_FOLDER / "markers_without_header.csv" +MARKERS_XLSX = DATA_FOLDER / "markers.xlsx" +MARKERS_TRC = DATA_FOLDER / "markers.trc" +ANALOGS_XLSX = DATA_FOLDER / "analogs.xlsx" +ANALOGS_STO = DATA_FOLDER / "inverse_dyn.sto" +ANALOGS_MOT = DATA_FOLDER / "inverse_kin.mot" +EXPECTED_VALUES_CSV = DATA_FOLDER / "is_expected_array_val.csv" + +MARKERS_DATA = Markers.from_c3d( + MARKERS_ANALOGS_C3D, + usecols=["CLAV_post", "PSISl", "STERr", "CLAV_post"], + prefix_delimiter=":", +) +ANALOGS_DATA = Analogs.from_c3d( + MARKERS_ANALOGS_C3D, + usecols=["EMG1", "EMG10", "EMG11", "EMG12"], + prefix_delimiter=".", +) + +EXPECTED_VALUES = pd.read_csv( + EXPECTED_VALUES_CSV, + index_col=[0], + converters={"shape_val": eval, "first_last_val": eval}, +).to_dict("index") diff --git a/tests/data/analogs.xlsx b/tests/data/analogs.xlsx new file mode 100644 index 0000000..3b9a9a8 Binary files /dev/null and b/tests/data/analogs.xlsx differ diff --git a/tests/data/inverse_dyn.sto b/tests/data/inverse_dyn.sto new file mode 100644 index 0000000..8b2a1be --- /dev/null +++ b/tests/data/inverse_dyn.sto @@ -0,0 +1,99 @@ +Inverse Dynamics Generalized Forces,,,,,,,,,,,,,,,,,,,,,,,,, +version=1,,,,,,,,,,,,,,,,,,,,,,,,, +nRows=250,,,,,,,,,,,,,,,,,,,,,,,,, +nColumns=26,,,,,,,,,,,,,,,,,,,,,,,,, +inDegrees=no,,,,,,,,,,,,,,,,,,,,,,,,, +endheader,,,,,,,,,,,,,,,,,,,,,,,,, +time,thorax_tilt_moment,thorax_list_moment,thorax_rotation_moment,thorax_tx_force,thorax_ty_force,thorax_tz_force,box_rotX_moment,box_rotY_moment,box_rotZ_moment,box_transX_force,box_transY_force,box_transZ_force,sternoclavicular_r1_moment,sternoclavicular_r2_moment,sternoclavicular_r3_moment,Acromioclavicular_r1_moment,Acromioclavicular_r2_moment,Acromioclavicular_r3_moment,shoulder_plane_moment,shoulder_ele_moment,shoulder_rotation_moment,elbow_flexion_moment,pro_sup_moment,hand_r_Add_moment,hand_r_Flex_moment +1.56,-4.47756531,-8.90170048,-3.58653785,-0.26786448,215.9231827,-1.53266998,0,0,0,0,0,59.682,-3.33127845,5.51859618,5.53382759,3.49994759,-0.33430351,5.32411736,-2.97352153,5.27556335,-0.58664654,2.36539107,-0.07423056,0.22831322,-0.03111117 +1.57,-4.18729272,-8.41663267,-3.50390882,-13.60558121,218.45488923,3.91734389,-7.68E-06,8.98E-06,-1.749E-05,-0.68894632,-0.36120058,59.55622157,-3.13470986,5.29393111,4.58826066,2.97639109,0.13584015,4.40661944,-2.20246888,4.75875432,-0.215709,2.2908386,-0.07415507,0.1823854,0.00275531 +1.58,-3.91450668,-8.13887099,-3.9534653,-25.47074046,221.35518298,9.30025322,-2.321E-05,2.021E-05,-2.68E-05,-1.26759291,-0.67395377,59.51261457,-3.43473999,5.22005049,3.69956282,2.27253839,0.23742311,3.66435524,-1.85347746,4.0894497,-0.02370232,2.1566052,-0.07349421,0.14223965,0.00477863 +1.59,-3.43231283,-8.1441959,-4.87777294,-34.51488577,225.05681513,13.76263351,-5.036E-05,3.449E-05,-1.961E-05,-1.59982747,-0.85826893,59.61372256,-4.19634143,5.33727442,3.19554587,1.66928308,-0.02106696,3.36159122,-2.02180446,3.57438893,-0.04938461,2.09910413,-0.07549314,0.11886143,-0.02473195 +1.6,-3.14476847,-8.22801502,-5.91181589,-40.8577789,228.91796083,16.29794388,-8.657E-05,5.065E-05,1.205E-05,-1.51426727,-0.78709916,59.87378672,-4.98538123,5.41429844,2.77425055,1.15533151,-0.31553683,3.10857641,-2.23332545,3.13414153,-0.12378375,2.09626643,-0.06943718,0.10303815,-0.0913479 +1.61,-3.47045156,-8.15528308,-6.67418718,-45.01061417,231.94362822,16.29033864,-0.00012212,6.459E-05,7.379E-05,-0.82293653,-0.31502427,60.241785,-5.35339188,5.22068552,2.08743548,0.67146409,-0.33148839,2.48110214,-2.00729525,2.62874611,-0.08714589,2.08585872,-0.06537141,0.08231791,-0.19121372 +1.62,-4.07351078,-7.98034261,-6.9492838,-46.1197882,233.5518804,13.97710735,-0.00014084,6.785E-05,0.00016607,0.63368147,0.64838043,60.61203476,-5.16571757,4.8513105,1.49607533,0.47104914,-0.06434232,1.80674583,-1.46659851,2.33694305,0.03043729,2.11579521,-0.08901851,0.07396298,-0.2599977 +1.63,-4.35034848,-8.01723016,-6.92461255,-43.1305423,233.8585958,10.38657315,-0.00012534,4.958E-05,0.0002821,2.93365708,2.06789449,60.8502524,-4.75909979,4.64457264,1.55501826,0.72357282,0.19843335,1.72659486,-1.15743398,2.54614125,0.10355333,2.24127078,-0.10784989,0.08372388,-0.25975902 +1.64,-4.14573492,-8.55132548,-7.27248483,-36.53266922,233.45566479,7.09005917,-6.759E-05,5.48E-06,0.00040746,6.03886022,3.77135484,60.83456758,-4.88849052,4.88559777,2.26074323,1.07902164,-0.0022812,2.45605968,-1.63259778,3.00745261,0.00495621,2.36137491,-0.04722284,0.08190584,-0.26861622 +1.65,-3.39269238,-9.65546948,-8.43084979,-26.93452621,232.68404692,5.55625999,2.059E-05,-4.994E-05,0.00052129,9.79219713,5.51482432,60.52284394,-6.09204197,5.69033943,3.49884048,1.22188891,-1.02066101,4.04213186,-3.26454,3.44515933,-0.37254546,2.32212423,0.12602085,0.05517061,-0.34560999 +1.66,-2.24997003,-11.03415001,-10.26231205,-15.4656251,231.50460486,6.41497589,0.00010673,-8.242E-05,0.00060087,13.9322242,7.07933863,60.01837721,-8.29194011,6.85639015,4.99069005,1.04485347,-2.81435095,6.17840842,-5.87841672,3.67842241,-1.03628739,2.03294646,0.35009982,0.00433038,-0.47244801 +1.67,-1.23810058,-12.192853,-12.45397875,-4.90776413,230.1988968,9.40705387,0.00015199,-5.789E-05,0.00062992,18.10619838,8.33123686,59.57612463,-11.08935014,7.97954106,6.13789623,0.43881026,-5.00526863,8.12433725,-8.80013823,3.46667561,-1.86632115,1.53634707,0.53736534,-0.06914796,-0.6437236 +1.68,-0.62714287,-12.94544297,-15.00012524,2.09565892,229.54727316,14.25017171,0.0001315,3.259E-05,0.00060576,21.90263088,9.22495656,59.51174181,-14.33204548,8.91332031,6.59351853,-0.69451909,-7.34017739,9.47638745,-11.63463574,2.68881852,-2.74053098,0.92885238,0.70208782,-0.16776873,-0.90210154 +1.69,-0.22144191,-13.3840326,-17.70993416,5.28174392,229.90871146,20.40067501,4.277E-05,0.00016931,0.00053794,24.94093762,9.77217813,60.03854463,-17.75555957,9.71696519,6.60588614,-2.10228113,-9.62424682,10.41586396,-14.27474832,1.6413276,-3.57732672,0.32340023,0.88256084,-0.28328038,-1.23518711 +1.7,0.18316728,-13.47947093,-19.70672866,6.0220219,230.96199762,26.30026557,-0.000102,0.00032303,0.00043882,26.99414241,9.99997426,61.14048906,-20.46800947,10.29431175,6.63799647,-3.11247134,-11.33005183,11.1373925,-16.26894698,0.96031857,-4.25175832,-0.04268358,1.01541056,-0.37769499,-1.51940734 +1.71,0.62137763,-13.17112363,-20.28395092,5.4026403,232.44574784,30.54143205,-0.00028554,0.00047519,0.00031582,28.04935176,9.91667393,62.58722035,-21.71218329,10.56253279,6.96233861,-3.2362967,-11.97577882,11.68715467,-17.15386699,1.08886219,-4.63355007,0.05374269,1.04455154,-0.41373758,-1.63371056 +1.72,0.999389,-12.59514269,-19.90542118,3.45078036,234.37206183,33.39324138,-0.00048551,0.00062557,0.0001735,28.23648302,9.50628952,64.07523734,-21.81452285,10.69096523,7.46603929,-2.76147551,-11.7780009,12.09157949,-17.16030952,1.77485988,-4.74151121,0.49500933,1.00972156,-0.39589416,-1.59210436 +1.73,1.34686548,-11.99555689,-19.63433895,-0.31402237,237.14909214,35.77282277,-0.00067027,0.00078838,2.107E-05,27.70091268,8.75773286,65.37245709,-21.70329812,10.91880393,7.98326596,-2.21539264,-11.36950385,12.46227358,-16.95769038,2.55489532,-4.76843687,1.02332166,0.94636508,-0.35516426,-1.51816145 +1.74,1.76990184,-11.52428682,-19.88281395,-5.61494656,240.7131115,37.61500852,-0.00080395,0.0009782,-0.00012734,26.57977034,7.71367048,66.3629592,-21.7659012,11.27471398,8.48917612,-1.75011139,-11.07212224,12.85774283,-16.87167668,3.26402479,-4.86886671,1.49688039,0.84221167,-0.31834732,-1.50011911 +1.75,2.08020591,-11.24443554,-20.47055666,-11.80668809,244.19196651,38.33419481,-0.00086194,0.00119302,-0.00026088,25.06998267,6.50154324,67.00326151,-21.8584453,11.59561646,8.76450253,-1.45961558,-10.85128378,13.0374861,-16.75860963,3.72704959,-5.02115413,1.83387667,0.69258229,-0.29669941,-1.52204329 +1.76,2.13866578,-11.23358222,-20.99813788,-17.93584862,246.79023936,37.41835314,-0.00084339,0.00140799,-0.0003758,23.41701622,5.30275322,67.27953463,-21.69802468,11.72470163,8.70282163,-1.30983266,-10.54832968,12.83624021,-16.39182051,3.9220503,-5.14096657,2.05582802,0.5194538,-0.28228111,-1.53192521 +1.77,2.03331538,-11.60120243,-21.21077136,-22.98945975,248.31423524,34.86452155,-0.00077027,0.00158368,-0.00047032,21.80721736,4.26873795,67.22325081,-21.20242849,11.66302559,8.43924136,-1.18658111,-10.09618837,12.37138548,-15.76132,3.99357701,-5.18378097,2.23112081,0.38927005,-0.27788997,-1.53091572 +1.78,2.04111435,-12.40813592,-21.23706594,-26.488015,249.44274742,31.43834613,-0.00067427,0.00168059,-0.00054013,20.30185337,3.45974936,66.96101909,-20.65763628,11.59095505,8.26855357,-0.99746425,-9.62325861,11.99294091,-15.15104817,4.14827812,-5.21405244,2.46050272,0.36394319,-0.29153161,-1.55446949 +1.79,2.01924602,-13.36546779,-21.10235675,-28.60148793,250.35012365,27.96172869,-0.00058056,0.00167354,-0.00058028,18.860762,2.85676956,66.71752723,-20.14872385,11.54384666,8.13168583,-0.81881023,-9.1368096,11.67526656,-14.56695245,4.3381232,-5.20952688,2.77490069,0.44779255,-0.3258402,-1.60266079 +1.8,1.52648305,-13.96629231,-20.69322132,-29.5174483,250.37760347,25.09856635,-0.00049876,0.00156149,-0.00058901,17.40338236,2.41135842,66.72762008,-19.53842089,11.40193146,7.71782299,-0.83707759,-8.57800778,11.11704306,-13.82279365,4.30455971,-5.08323911,3.06001916,0.59229071,-0.37822221,-1.64918515 +1.81,0.73486033,-14.00364135,-20.08145463,-29.05938963,249.69392175,23.32732853,-0.00042416,0.00136701,-0.00056904,15.8602445,2.07890792,67.1268001,-18.88781636,11.21820526,7.22239439,-0.93553321,-8.03756068,10.50406255,-13.07184613,4.17014672,-4.92628442,3.28421905,0.7433142,-0.43520184,-1.69289171 +1.82,-0.02338823,-13.5805396,-19.41160922,-27.57525923,248.84939689,22.58282573,-0.00034532,0.001126,-0.00052637,14.19466524,1.82763166,67.91535119,-18.29383208,11.07774656,6.85581098,-0.98157754,-7.57425076,10.03292688,-12.4490888,4.09972031,-4.82127033,3.4919159,0.88628768,-0.48367835,-1.75657793 +1.83,-0.61301154,-12.94272108,-18.6710502,-25.72858678,247.86298502,22.22001943,-0.00025293,0.00087512,-0.00046872,12.40285556,1.63938941,69.00063518,-17.6712497,10.94668839,6.55766151,-0.97414271,-7.12023996,9.61979159,-11.85323116,4.07348555,-4.72471232,3.69329194,1.02549739,-0.51818243,-1.84868255 +1.84,-0.90175882,-12.45946125,-17.88146887,-24.09420465,246.79931072,21.60070128,-0.00014686,0.00064203,-0.00040385,10.51794039,1.50676098,70.25691281,-16.99911628,10.83023298,6.27655005,-0.94834808,-6.64653029,9.21900073,-11.23993096,4.05905008,-4.59692714,3.88876671,1.17463977,-0.54775722,-1.95380563 +1.85,-0.94068552,-12.39599577,-17.12352353,-23.40708454,246.00102885,20.31607209,-3.899E-05,0.00043969,-0.00033849,8.62197392,1.42413378,71.55468244,-16.33188887,10.75429866,5.91853784,-0.98841138,-6.1344657,8.76101852,-10.57258621,3.99399703,-4.38774969,4.12064732,1.35859184,-0.58984323,-2.05896651 +1.86,-0.84072915,-12.72731352,-16.38794387,-23.60383147,245.60549785,18.4247755,4.948E-05,0.00026721,-0.00027903,6.83955225,1.37958866,72.7602335,-15.68752801,10.72414222,5.51666306,-1.07414892,-5.60458092,8.27719475,-9.8822321,3.8996398,-4.12207725,4.3980136,1.5515998,-0.64059236,-2.15214737 +1.87,-0.70684466,-13.26917233,-15.88577871,-24.21611481,245.76185728,16.93690214,9.69E-05,0.00011809,-0.00023157,5.30769585,1.3600754,73.73697616,-15.30343637,10.83751402,5.15447179,-1.26760408,-5.25133919,7.91791303,-9.41722486,3.75516791,-3.9334024,4.65850511,1.70200936,-0.68547397,-2.21421621 +1.88,-0.81527486,-13.67246034,-15.70029972,-25.07070236,246.32799278,16.66547886,9.333E-05,-1.039E-05,-0.00019773,4.15180373,1.3664874,74.35779037,-15.24329153,11.08802843,4.73850798,-1.6696061,-5.12528037,7.6191087,-9.20097253,3.45593812,-3.83637149,4.84782777,1.79073298,-0.72332459,-2.25320299 +1.89,-1.39552669,-13.6045298,-15.55524593,-25.90093158,246.74951739,17.44708238,4.755E-05,-0.00011447,-0.000171,3.46147745,1.41516005,74.52159197,-15.20687774,11.31338748,4.18483099,-2.19442163,-5.04588682,7.2106806,-8.98177012,3.00826182,-3.72555691,4.93918605,1.82963093,-0.76389339,-2.29951934 +1.9,-2.32232953,-13.19011691,-15.43362785,-26.65523315,247.17687372,18.99737982,-1.515E-05,-0.00018917,-0.00014074,3.23055819,1.51358799,74.20318162,-15.14804549,11.5095591,3.60163255,-2.72169349,-4.96907816,6.7611765,-8.7444829,2.53636754,-3.61325517,4.9899924,1.84010238,-0.80928551,-2.34592907 +1.91,-3.38604088,-12.77649157,-15.35258075,-27.56624722,247.82314521,20.68436613,-6.169E-05,-0.00023608,-0.00010043,3.30037926,1.63395361,73.52569661,-15.05884244,11.65248976,3.06797126,-3.15547649,-4.88276212,6.3125009,-8.49020555,2.12545331,-3.52941392,5.07842504,1.8156953,-0.84287497,-2.3618184 +1.92,-4.44948843,-12.60242411,-15.04454823,-28.82102614,248.25164458,21.32331162,-6.411E-05,-0.00026363,-5.1E-05,3.3917552,1.71779645,72.73581271,-14.68584225,11.58495541,2.56085266,-3.38224318,-4.64525827,5.76156939,-8.0304635,1.81267385,-3.38586048,5.20437395,1.76579669,-0.85824159,-2.33583156 +1.93,-5.35297643,-12.82362423,-14.42344269,-30.78152717,248.23814304,20.23138533,-1.409E-05,-0.00027807,1.93E-06,3.22213818,1.70913401,72.04631487,-13.97399751,11.28762266,2.05152963,-3.41783336,-4.20679954,5.08822856,-7.31585076,1.59014297,-3.11374371,5.34743533,1.76029321,-0.87478806,-2.28001017 +1.94,-5.94297385,-13.35087308,-13.64509962,-33.75785035,248.10536856,17.58407133,7.11E-05,-0.00027797,5.176E-05,2.6229101,1.59022306,71.50469417,-13.10717016,10.82988026,1.56593739,-3.31254758,-3.66177386,4.35928585,-6.470581,1.44331168,-2.77255351,5.56494409,1.8056156,-0.88760936,-2.21696686 +1.95,-6.36092387,-13.79463407,-12.87536058,-37.53892107,247.66570346,14.27825006,0.00015832,-0.00025923,9.453E-05,1.58980228,1.40948192,70.99609801,-12.24458719,10.22694417,0.99462406,-3.22745641,-3.13536654,3.52383442,-5.59280881,1.20921714,-2.40261901,5.78734717,1.8496125,-0.88694276,-2.16263895 +1.96,-6.51802609,-13.88261151,-12.14864814,-40.92123965,247.0019184,11.3621611,0.00021698,-0.00022062,0.00013233,0.24718041,1.27847967,70.32238208,-11.44249531,9.65787203,0.56502023,-3.08992311,-2.68343213,2.84713402,-4.8509578,1.04536282,-2.10147084,5.97177495,1.88375872,-0.88588048,-2.11442682 +1.97,-6.38122237,-13.5248409,-11.55969672,-43.17408887,246.58507012,9.72747401,0.00023288,-0.00016246,0.00017281,-1.23474689,1.29180092,69.29446714,-10.77437934,9.30734509,0.4434864,-2.85218402,-2.32604334,2.53011708,-4.35710012,1.09174074,-1.93556707,6.16717223,1.9135479,-0.886984,-2.06151054 +1.98,-6.22005911,-12.71720583,-11.18165092,-44.29543189,246.15037618,9.84082237,0.00021045,-8.297E-05,0.00022304,-2.70107406,1.40155297,67.81274956,-10.24984723,9.14159071,0.4304896,-2.68152854,-2.07684142,2.40924523,-4.05559111,1.1664688,-1.84699528,6.27910224,1.91453314,-0.87726053,-1.98842417 +1.99,-6.14232866,-11.59040585,-10.72194482,-44.67136411,245.57813694,10.65700248,0.0001666,2.054E-05,0.00028224,-4.04699094,1.40304957,65.90536945,-9.57365794,8.94969043,0.40533945,-2.46979462,-1.73170753,2.25665417,-3.6457508,1.2616982,-1.66610682,6.35295194,1.87250684,-0.84771958,-1.88557754 +2,-6.20796911,-10.42482087,-9.97762562,-45.14847213,244.9549685,10.73972162,0.00012396,0.00014426,0.00033853,-5.25349375,1.10778049,63.72333055,-8.56875893,8.49846355,0.2291752,-2.13113888,-1.15846857,1.82660549,-2.88640359,1.32746872,-1.26180567,6.48068272,1.76410536,-0.7912144,-1.76991838 +2.01,-6.34968937,-9.70662226,-9.10163745,-46.11886363,244.23893688,9.67736014,0.00010411,0.00027094,0.00037254,-6.37368937,0.5181098,61.50493558,-7.44580848,7.85157878,-0.1239643,-1.79463862,-0.50789819,1.1587324,-1.94168823,1.27576391,-0.71726662,6.59523007,1.62146234,-0.72261339,-1.6581119 +2.02,-6.23042765,-9.95574733,-8.41946878,-47.134863,243.73853257,8.00767096,0.0001184,0.00037139,0.00036727,-7.43862904,-0.18920045,59.50103208,-6.6219393,7.41479495,-0.36810434,-1.56461775,-0.06107856,0.70251543,-1.28813921,1.22997453,-0.32683171,6.640128,1.53772895,-0.67558648,-1.55195487 +2.03,-5.89386396,-10.96999843,-7.89518032,-47.91761148,243.35752531,5.76916654,0.00015905,0.00041681,0.00031696,-8.37986179,-0.80152176,57.86556519,-6.09221429,7.20919683,-0.45495625,-1.40435186,0.19181567,0.49868545,-0.9357642,1.2370684,-0.1193163,6.67054712,1.50870058,-0.64503108,-1.45741862 +2.04,-5.68811447,-12.11521073,-7.44251914,-48.19599899,242.40058489,3.17350045,0.00019797,0.00039584,0.00022794,-9.05399769,-1.18823434,56.58154867,-5.72143791,7.053037,-0.55289433,-1.34110583,0.32533017,0.33325632,-0.71604819,1.17547263,0.00828473,6.64824392,1.47766905,-0.61909254,-1.39134755 +2.05,-5.75484843,-12.92169041,-7.06652163,-47.42734145,240.55275196,0.71623162,0.00020069,0.00032177,0.00011461,-9.35348203,-1.35199081,55.52826043,-5.44839078,6.9363189,-0.64293768,-1.34164866,0.37911573,0.21427557,-0.59348525,1.07602202,0.07565953,6.57195583,1.4257783,-0.59317571,-1.34157108 +2.06,-6.18913415,-13.02408442,-6.57137453,-45.31936906,237.60180193,-1.84642047,0.00015082,0.00022399,-9.4E-07,-9.34320585,-1.38848119,54.65952167,-5.0152427,6.73822708,-0.70495131,-1.24262231,0.5403865,0.08149353,-0.35956916,1.04735394,0.22207391,6.50868654,1.34568968,-0.55311274,-1.26990768 +2.07,-6.98454835,-12.4426871,-5.98276124,-41.95377942,233.66038566,-4.61446929,6.411E-05,0.00013474,-8.909E-05,-9.30480201,-1.37511722,54.10167394,-4.40061723,6.44885172,-0.72905328,-1.01725097,0.83091605,-0.06545058,0.00754089,1.11538602,0.46798053,6.47342075,1.2596667,-0.50105778,-1.17394829 +2.08,-7.9355804,-11.65969424,-5.73074675,-37.7562524,229.62181311,-7.0061951,-2.415E-05,7.762E-05,-0.0001239,-9.57285907,-1.30242014,54.07223567,-4.01483923,6.32639002,-0.64576552,-0.83537933,0.98356718,-0.04793528,0.17074231,1.22159424,0.57022597,6.4773352,1.2113882,-0.46284754,-1.11182831 +2.09,-9.08618448,-10.90374991,-5.9480771,-33.62589104,225.84038618,-9.08946466,-8.825E-05,5.484E-05,-0.00010077,-10.25118681,-1.11669663,54.71060774,-3.98197343,6.30750506,-0.67804565,-0.88559342,0.92418497,-0.06498714,0.14343552,1.13702285,0.54117728,6.48217808,1.1957305,-0.45156049,-1.13217052 +2.1,-10.4614366,-10.09462075,-6.22654216,-30.03047182,222.23587634,-11.83298525,-0.00013054,4.278E-05,-4.08E-05,-11.09666885,-0.7870913,55.96282533,-3.93792376,6.08248216,-1.01949827,-1.07723889,0.90504575,-0.41910275,0.29946508,0.81494734,0.66014557,6.47794829,1.18647196,-0.44991157,-1.20431337 +2.11,-11.62663579,-9.46091018,-6.36735825,-26.16754888,219.27455972,-15.17768607,-0.00017414,6.75E-06,2.462E-05,-11.69895605,-0.30125408,57.61331304,-3.78077358,5.79289394,-1.37701747,-1.21743251,0.95935424,-0.82133409,0.54714348,0.51551421,0.85115605,6.45510243,1.18495414,-0.4464432,-1.27323569 +2.12,-12.30665102,-9.26977112,-6.51520163,-21.50677038,217.69288103,-18.48863274,-0.00024105,-7.599E-05,7.193E-05,-11.73210868,0.3669489,59.43214009,-3.69173695,5.70519612,-1.50076277,-1.26360125,0.95318215,-0.9665841,0.62639191,0.38585699,0.90416218,6.47768056,1.17026212,-0.43445489,-1.32281257 +2.13,-12.87375135,-9.38161779,-6.71802716,-16.57029536,217.15442119,-21.88664596,-0.00033601,-0.00020244,9.049E-05,-11.06170638,1.24062173,61.29094991,-3.67181205,5.68650149,-1.62972923,-1.34704002,0.9192868,-1.09708809,0.66845535,0.24203265,0.93236247,6.54101745,1.14541293,-0.42208382,-1.37029519 +2.14,-13.75005071,-9.64161024,-7.02999068,-11.80226439,216.74143007,-25.28665395,-0.00044209,-0.00034492,7.757E-05,-9.75941138,2.29015958,63.160168,-3.74909382,5.59579834,-2.01109181,-1.64953225,0.84127234,-1.43990693,0.75206695,-0.1424557,1.01259596,6.50979047,1.1479512,-0.42980725,-1.45057059 +2.15,-14.92500376,-10.05835067,-7.49242722,-7.3806507,216.54486491,-28.25148272,-0.00052586,-0.00046308,3.358E-05,-8.09996586,3.41590067,65.03858753,-4.02342379,5.48437129,-2.55052656,-2.12779781,0.64652547,-1.90912982,0.77580845,-0.71849912,1.04264818,6.45922975,1.15835625,-0.44684014,-1.58253979 +2.16,-16.23501222,-10.55409798,-7.96823961,-3.69117571,216.96208847,-30.53202118,-0.00055345,-0.00052258,-3.557E-05,-6.49501651,4.46468928,66.88814855,-4.46049967,5.37202788,-3.11305267,-2.63134988,0.37005944,-2.4161186,0.73770192,-1.33926491,1.00156775,6.53503648,1.16501602,-0.46339649,-1.75577776 +2.17,-17.71168038,-10.88446008,-8.32822497,-1.39556959,217.4651109,-31.66576668,-0.0005127,-0.00051335,-0.00011151,-5.33817712,5.23820942,68.60048923,-4.98860619,5.15711418,-3.81359395,-3.22168748,0.04922972,-3.07329669,0.71883831,-2.09263664,0.96702688,6.61543775,1.18169842,-0.48968314,-1.95481633 +2.18,-19.26839283,-10.87085658,-8.48498202,-0.72550054,217.55123178,-31.1247283,-0.00042245,-0.00045136,-0.0001657,-4.83072578,5.53768473,70.00160688,-5.52505982,4.777769,-4.6667435,-3.90433573,-0.30679904,-3.89547002,0.73852298,-3.00447043,0.95790826,6.54040848,1.16712561,-0.51727646,-2.15308715 +2.19,-20.50698176,-10.59671954,-8.38352732,-1.50843329,217.87024332,-29.04965143,-0.00031541,-0.00035835,-0.00017589,-4.92164392,5.27602309,70.88314259,-5.96458327,4.34003951,-5.37813335,-4.42486387,-0.64167726,-4.63230892,0.74789339,-3.78868486,0.91906093,6.42890969,1.10008566,-0.52973314,-2.33050451 +2.2,-21.31074593,-10.20413518,-8.19064888,-4.07216908,218.85809554,-25.94799056,-0.00021421,-0.00024014,-0.00013966,-5.43101502,4.52610463,71.0578066,-6.30569165,3.92190731,-5.88319349,-4.73622405,-0.91663539,-5.21452655,0.74793588,-4.36005138,0.85859276,6.38801088,1.02044856,-0.53566969,-2.49903276 +2.21,-21.90766016,-9.68905754,-8.09498343,-8.89003125,219.95225048,-22.48129306,-0.00012769,-8.644E-05,-7.174E-05,-6.21975018,3.43872863,70.44980021,-6.52715377,3.4311064,-6.41173162,-4.99623503,-1.07706516,-5.84349098,0.87034529,-4.88785536,0.91096543,6.35748295,0.94298818,-0.53706685,-2.6519824 +2.22,-22.21780409,-9.06264156,-8.01035268,-15.30620029,220.75092133,-19.51614776,-6.17E-05,0.00011053,6.85E-06,-7.21467652,2.1622512,69.16886241,-6.46140554,2.82770493,-6.91457886,-5.10488859,-1.02046556,-6.49816937,1.21027278,-5.27898076,1.16089673,6.32270153,0.8236045,-0.51232423,-2.7351039 +2.23,-21.9156584,-8.42167632,-7.81224862,-21.9130131,221.18055418,-17.60612607,-2.344E-05,0.00034115,7.717E-05,-8.32476708,0.83138968,67.4861244,-6.02745482,2.21432293,-7.1336777,-4.87736424,-0.74806662,-6.94143954,1.6768759,-5.31726546,1.52206232,6.28285647,0.63128347,-0.45529096,-2.72701352 +2.24,-20.98810342,-7.79938945,-7.624081,-28.05180533,221.00629407,-16.72406874,-1.726E-05,0.00058094,0.0001268,-9.42172486,-0.44065887,65.72258268,-5.40146234,1.65728867,-7.0924837,-4.4298414,-0.39857955,-7.15168017,2.13031444,-5.09530617,1.87525852,6.19760901,0.38866119,-0.37788838,-2.67037097 +2.25,-19.756296,-7.12498749,-7.66931383,-33.98726719,220.09765575,-16.71343119,-4.125E-05,0.00080221,0.00015207,-10.40815038,-1.57480994,64.13359747,-4.84550548,1.11050387,-7.07748912,-4.05086091,-0.10101143,-7.35963334,2.53666157,-4.9142407,2.19784173,6.07844532,0.15854376,-0.30228437,-2.62549326 +2.26,-18.41220584,-6.39206079,-8.01972462,-39.5823855,218.81615346,-17.25758759,-8.76E-05,0.00098344,0.00015761,-11.24655756,-2.51922091,62.84751941,-4.51240054,0.58392359,-7.19566811,-3.85998067,0.06746419,-7.6505618,2.85344482,-4.89935041,2.44871085,5.97057076,-0.03113645,-0.25048523,-2.62680034 +2.27,-16.99882272,-5.57927523,-8.47097659,-44.13605631,217.54287923,-18.17768476,-0.0001449,0.00111385,0.00015064,-11.89740736,-3.26244597,61.88174331,-4.30933054,0.06269471,-7.36191059,-3.74332512,0.14905732,-7.9776891,3.10427951,-4.95587038,2.63838903,5.88351683,-0.20037407,-0.22136957,-2.67112615 +2.28,-15.5854165,-4.70195595,-8.74746496,-46.99393305,216.4513802,-19.36013068,-0.00020208,0.00119272,0.00013412,-12.27562628,-3.86012454,61.21604995,-4.05161875,-0.44834624,-7.46838011,-3.56180857,0.25869603,-8.25857732,3.361714,-4.93376694,2.83469402,5.8116211,-0.35848995,-0.19714587,-2.70634806 +2.29,-14.39986079,-3.88207984,-8.8699824,-48.18328942,215.5336367,-20.33195995,-0.00025178,0.00122432,0.00010631,-12.31591276,-4.40580848,60.84007373,-3.78458919,-0.86047686,-7.53363975,-3.39135274,0.39024833,-8.46013359,3.59609834,-4.86219471,3.02323127,5.74956914,-0.47620717,-0.1742153,-2.7007569 +2.3,-13.62036014,-3.18667927,-8.94874836,-48.01044196,214.71871135,-20.8044065,-0.00028924,0.00121406,6.653E-05,-12.06186108,-4.96314945,60.72916123,-3.62067374,-1.1577194,-7.57921458,-3.2832644,0.46338137,-8.59689556,3.74112392,-4.80614841,3.13622064,5.7085533,-0.56783696,-0.15251603,-2.69364845 +2.31,-13.28467705,-2.58600157,-8.92644422,-46.61151551,213.52337966,-20.97171821,-0.00030866,0.00116833,1.943E-05,-11.6467175,-5.53380883,60.81200531,-3.51233141,-1.42359509,-7.60311658,-3.20058927,0.46774968,-8.69983489,3.81003277,-4.7764256,3.17554542,5.59285743,-0.64709867,-0.1475983,-2.71644528 +2.32,-13.39800567,-2.09385492,-8.82245996,-44.7105839,211.77210163,-21.15231524,-0.00030277,0.00109516,-2.652E-05,-11.17627719,-6.0696517,60.98983055,-3.4552216,-1.69451495,-7.6717798,-3.1752757,0.4307873,-8.83481407,3.85235129,-4.81293628,3.18818201,5.42745292,-0.68196391,-0.16686132,-2.76215158 +2.33,-13.89375792,-1.71207079,-8.68327264,-43.04919311,209.73304959,-21.53137061,-0.00026687,0.0010017,-6.394E-05,-10.64623267,-6.50354603,61.18813283,-3.46755003,-1.98194966,-7.81802821,-3.20916736,0.37666116,-9.04255247,3.90466806,-4.9188883,3.20655077,5.32916968,-0.67554132,-0.18995168,-2.82664333 +2.34,-14.59793047,-1.40556913,-8.44733861,-41.45404007,207.522275,-21.9680238,-0.00020236,0.00088967,-9.035E-05,-9.97568516,-6.79425202,61.407,-3.4910036,-2.2705139,-7.96561621,-3.24722073,0.30851871,-9.25200455,3.94439834,-5.03846482,3.20936307,5.23181175,-0.64295339,-0.21689636,-2.91444038 +2.35,-15.36725429,-1.14941935,-8.07953559,-39.66707383,205.35179736,-22.20048165,-0.00011584,0.0007532,-0.00010892,-9.10079132,-6.96771456,61.73788867,-3.4828832,-2.51521448,-8.05607097,-3.26745693,0.23079906,-9.39130551,3.94562737,-5.13430425,3.17735138,5.06325799,-0.5838503,-0.2628712,-3.01563131 +2.36,-16.09244915,-0.96504753,-7.5804576,-37.73033997,203.63491414,-22.1217046,-1.473E-05,0.00058415,-0.00012467,-8.04574326,-7.10818185,62.29946294,-3.41237542,-2.66140297,-8.05629651,-3.24686536,0.18820884,-9.41614443,3.92619576,-5.15454099,3.13436555,4.88271085,-0.51020945,-0.31150675,-3.08592214 +2.37,-16.66070135,-0.90654862,-6.99505849,-35.83518998,202.7273645,-21.83400109,9.465E-05,0.00038348,-0.00013821,-6.95688044,-7.29754924,63.11134882,-3.27662502,-2.67706424,-7.93433968,-3.14714111,0.21081758,-9.30153144,3.90100077,-5.04490751,3.09616983,4.7920338,-0.44345492,-0.33714694,-3.10260136 +2.38,-17.08653623,-0.91244766,-6.39068607,-34.34031593,202.32339645,-21.73786156,0.0002036,0.00016771,-0.00014513,-6.08313809,-7.57386775,64.03914602,-3.07735014,-2.68309935,-7.77692819,-2.98122255,0.30087883,-9.16793183,3.92768709,-4.86111218,3.10622323,4.78114467,-0.38485412,-0.35110929,-3.10754672 +2.39,-17.44097397,-0.92052074,-5.81959739,-33.35334982,201.60239081,-22.00326903,0.00030016,-3.85E-05,-0.000142,-5.66856838,-7.94135664,64.90198783,-2.80922627,-2.79428329,-7.70128613,-2.83049766,0.43978601,-9.1312259,4.04137523,-4.71828794,3.19266214,4.70108031,-0.31401245,-0.38212424,-3.13115798 +2.4,-17.59847028,-1.08031516,-5.3567837,-32.72097968,200.70512702,-22.37514969,0.00037604,-0.00021768,-0.00013051,-5.81061434,-8.38220386,65.63887824,-2.52924903,-2.86930146,-7.60528994,-2.69293893,0.58465999,-9.0520447,4.14012405,-4.56638646,3.26972249,4.57434502,-0.23105433,-0.4269893,-3.16050136 +2.41,-17.41900743,-1.41677233,-4.94198698,-32.25087631,200.11650707,-22.95255346,0.00043311,-0.00036906,-0.00011492,-6.40957226,-8.84770696,66.35157898,-2.1990964,-2.82052386,-7.33805942,-2.44561732,0.76198709,-8.79752406,4.19456886,-4.25900429,3.31228575,4.50795888,-0.15848059,-0.46055869,-3.18525666 +2.42,-16.96962822,-1.7261015,-4.52948724,-31.7907112,199.44268729,-23.7454454,0.00048193,-0.00050321,-9.691E-05,-7.25162165,-9.26151706,67.1880968,-1.7985365,-2.72165271,-6.95804178,-2.11837597,0.96408082,-8.43387664,4.22965036,-3.85553849,3.33978613,4.4116035,-0.08615783,-0.48380621,-3.21483423 +2.43,-16.2661851,-1.84493798,-4.0874468,-31.35740264,198.61965001,-24.89445316,0.00053608,-0.00062843,-7.38E-05,-8.13200513,-9.54208533,68.20338668,-1.34019703,-2.62200388,-6.53556688,-1.73940559,1.19257595,-8.04332701,4.28190445,-3.40881485,3.38124849,4.29542553,-0.00180978,-0.50620797,-3.26079834 +2.44,-15.26364079,-1.77755284,-3.54459329,-30.99505685,198.1156837,-26.59581104,0.00060523,-0.00074378,-4.236E-05,-8.92774151,-9.62999147,69.32281912,-0.81393306,-2.4945841,-6.07044933,-1.27382114,1.49703211,-7.64051622,4.39968481,-2.87877781,3.48117218,4.29308193,0.07637411,-0.51904684,-3.30817602 +2.45,-14.0815347,-1.69674316,-2.98374782,-30.35174944,197.93571783,-28.12209866,0.00068829,-0.00084208,-4.05E-06,-9.61729939,-9.51956464,70.40360155,-0.33598934,-2.23988717,-5.58623728,-0.84259465,1.79104501,-7.18532712,4.480789,-2.35544103,3.55653527,4.32064214,0.13951809,-0.5207802,-3.33326076 +2.46,-12.97982595,-1.7150936,-2.35829393,-29.20552658,198.05155928,-29.63452806,0.00077203,-0.00091731,3.376E-05,-10.24572873,-9.27393372,71.32134719,0.14074033,-1.93996976,-5.1200247,-0.43984201,2.09118078,-6.73391645,4.56768964,-1.85831769,3.64108811,4.32128565,0.18064812,-0.50961055,-3.33175386 +2.47,-12.15741514,-1.99149924,-1.66712223,-27.68948309,198.6521176,-31.87201886,0.00083656,-0.0009703,6.037E-05,-10.83946127,-8.99791774,72.01284848,0.66702902,-1.71951784,-4.67812207,0.01085295,2.42412828,-6.35138102,4.71837362,-1.35526553,3.76933067,4.32963655,0.21056709,-0.49806839,-3.32306483 diff --git a/tests/data/inverse_kin.mot b/tests/data/inverse_kin.mot new file mode 100644 index 0000000..4c3a840 --- /dev/null +++ b/tests/data/inverse_kin.mot @@ -0,0 +1,99 @@ +Coordinates,,,,,,,,,,,,,,,,,,,,,,,,, +version=1,,,,,,,,,,,,,,,,,,,,,,,,, +nRows=250,,,,,,,,,,,,,,,,,,,,,,,,, +nColumns=26,,,,,,,,,,,,,,,,,,,,,,,,, +inDegrees=yes,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,, +Units are S.I. units (second, meters, Newtons, ...),,,,,,,,,,,,,,,,,,,,,, +Angles are in degrees.,,,,,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,, +endheader,,,,,,,,,,,,,,,,,,,,,,,,, +time,thorax_tilt,thorax_list,thorax_rotation,thorax_tx,thorax_ty,thorax_tz,sternoclavicular_r1,sternoclavicular_r2,sternoclavicular_r3,Acromioclavicular_r1,Acromioclavicular_r2,Acromioclavicular_r3,shoulder_plane,shoulder_ele,shoulder_rotation,elbow_flexion,pro_sup,hand_r_Add,hand_r_Flex,box_rotX,box_rotY,box_rotZ,box_transX,box_transY,box_transZ +1.56,-27.90807185,-5.48905351,15.19796161,-0.61262157,0.59430148,0.11781027,-4.51906909,-6.30882844,0,26.45087584,19.4733466,19.07716734,-54.83375662,52.62770795,4.24447963,42.06628447,-20.22393309,-11.12638637,5.76539899,-89.84738783,-181.00286714,18.49171606,0.19682993,0.0095494,0.22749238 +1.57,-28.10115004,-5.52733736,15.13052795,-0.61235021,0.59350449,0.11722554,-4.53724181,-6.17683574,0,26.60484204,19.47726344,19.10002346,-54.89512817,53.09440594,4.18960667,41.42117051,-20.09530759,-10.996419,6.41091215,-89.84859863,-181.00149101,18.4923649,0.19679952,0.00953395,0.22748828 +1.58,-28.27589336,-5.5648636,15.05747231,-0.61219918,0.59273837,0.11669383,-4.48880935,-6.09331933,0,26.72202409,19.46223332,19.04177504,-55.1439463,53.60642241,4.39761796,40.86891869,-19.43110249,-10.68318981,7.11930125,-89.84628055,-181.00478484,18.49033648,0.19676004,0.00950046,0.2274942 +1.59,-28.43655647,-5.59193757,14.98607214,-0.61220018,0.59205867,0.11625133,-4.41932478,-6.02958431,0,26.83497013,19.45047482,18.99513129,-55.45451702,54.11683619,4.92487571,40.3440917,-18.66963003,-10.64904508,7.1677367,-89.84124611,-181.00827251,18.49080077,0.19670913,0.00947025,0.22751124 +1.6,-28.58926312,-5.62607061,14.90054681,-0.61227839,0.59139644,0.11586262,-4.38246744,-5.96436432,0,26.9190792,19.43872901,18.95379153,-55.70484461,54.66131382,5.66382544,39.87831454,-19.54264918,-11.16404811,7.20633087,-89.8476761,-180.99824591,18.49136733,0.19664394,0.00946492,0.22747714 +1.61,-28.71034524,-5.63573539,14.80335299,-0.61264458,0.59079069,0.11560847,-4.30044336,-5.81682369,0,27.13524821,19.36884198,18.83738998,-56.02813704,55.09942068,6.55779032,39.34564718,-19.28908022,-11.79430043,6.87552206,-89.84849061,-180.99759203,18.49136626,0.19655373,0.00943186,0.22747218 +1.62,-28.84497846,-5.66701055,14.71606281,-0.61298842,0.59023452,0.11538067,-4.38171461,-5.69185294,0,27.50177405,19.47367868,18.62555508,-56.45217153,55.48031258,7.85385184,38.76384337,-20.48713847,-12.21084102,5.65746091,-89.85048645,-180.99561518,18.492439,0.19645163,0.00937616,0.22747697 +1.63,-28.94042917,-5.6956159,14.62543406,-0.61348313,0.58980957,0.11522281,-4.51690179,-5.57437943,0,27.83102224,19.59207544,18.48674622,-56.75545328,55.78537757,8.99585198,38.18624819,-21.55844459,-12.33915697,4.99671479,-89.85271619,-180.99491428,18.50053423,0.19634341,0.00932056,0.22748958 +1.64,-29.0048902,-5.71268187,14.53866938,-0.61415174,0.58952413,0.11514451,-4.6580172,-5.48046588,0,28.18191998,19.6791608,18.32938664,-57.08569373,56.03834329,10.2626468,37.67926067,-22.76400748,-12.37727601,4.26372865,-89.87438235,-181.00826677,18.52427238,0.19627489,0.00924491,0.22746494 +1.65,-29.05618664,-5.73281056,14.40872572,-0.61495791,0.58932931,0.11514817,-4.78407842,-5.38702781,0,28.46332294,19.72688288,18.27294039,-57.14794518,56.26455998,10.9933882,37.23594955,-22.85532059,-12.24352897,3.96995723,-89.89753931,-180.97076121,18.55564672,0.1963149,0.00929226,0.22756428 +1.66,-29.09221638,-5.74059975,14.2406041,-0.61587164,0.58921962,0.11524726,-4.8515591,-5.28045513,0,28.84264733,19.69047884,18.20453297,-57.23518274,56.36488517,11.95722403,36.9921709,-22.90815435,-12.24520685,3.40868295,-89.96313387,-180.91556102,18.60828766,0.19653945,0.00943999,0.22760431 +1.67,-29.11719272,-5.75792089,14.05651195,-0.61679365,0.58918476,0.11532508,-4.8677343,-5.25089374,0,29.0856667,19.6942627,18.13106065,-57.47409717,56.50209925,13.27144769,36.92691286,-22.77001356,-12.21182604,2.0875464,-89.98114191,-180.91253205,18.69837761,0.19694677,0.00969827,0.22776697 +1.68,-29.13536412,-5.81235771,13.86762293,-0.61770383,0.58916506,0.11532024,-4.8183538,-5.30368943,0,29.28120969,19.65296418,18.02995016,-57.97805102,56.61763854,15.31410722,37.08649839,-22.97443895,-12.2504123,0.08461239,-89.97367443,-180.94346086,18.83353517,0.19762714,0.0100995,0.22789826 +1.69,-29.15680155,-5.85063173,13.67184073,-0.61853553,0.58916808,0.11535294,-4.76475281,-5.36962079,0,29.41378254,19.58833879,17.99732368,-58.3160275,56.72336577,16.99521502,37.24846629,-22.85100625,-12.35077904,-1.69474721,-89.96600002,-180.95644295,18.98980417,0.19868549,0.0106574,0.22795535 +1.7,-29.17906806,-5.89801266,13.49718117,-0.61920117,0.58924387,0.11534262,-4.72881248,-5.52183515,0,29.58055566,19.63631727,17.90652095,-58.85810637,56.75056658,18.72052447,37.34235595,-23.31919906,-12.18870142,-2.96351557,-89.97125289,-180.93650102,19.15784658,0.20020559,0.01136111,0.22797828 +1.71,-29.17145629,-5.91346376,13.33279975,-0.61997455,0.5893112,0.11540236,-4.55861431,-5.56879518,0,29.71913549,19.51182791,17.81283315,-59.33070339,56.6846392,20.17066246,37.44208531,-24.02981571,-11.88564258,-3.55596521,-89.99120737,-180.90463613,19.33880261,0.20220442,0.01221172,0.22802829 +1.72,-29.16474147,-5.95576118,13.21409144,-0.62055517,0.58940263,0.11535747,-4.39430324,-5.66476816,0,29.83048517,19.42410685,17.78348846,-59.80530529,56.37334051,21.31412749,37.79873858,-24.81099678,-11.38846964,-3.93618722,-90.01883239,-180.86910268,19.52402325,0.20470191,0.01323436,0.22816402 +1.73,-29.15789571,-5.98557007,13.12964722,-0.62103623,0.58949384,0.11531715,-4.26491466,-5.637933,0,29.93584184,19.3160757,17.83061451,-59.99165446,55.8569784,21.77000702,38.3681817,-25.14490432,-11.0230658,-4.21516695,-90.02410555,-180.73854172,19.77744035,0.20735386,0.01439307,0.22844223 +1.74,-29.13525355,-6.02132783,13.08451509,-0.62147688,0.58962792,0.11527292,-4.05325164,-5.69452168,0,29.9574334,19.17957797,17.84360528,-60.37185925,55.20140649,22.17695823,39.3370011,-25.19894072,-10.90347833,-4.45293542,-90.09232582,-180.63880255,19.98694008,0.21074576,0.01571756,0.22877007 +1.75,-29.11610577,-6.03492442,13.07497563,-0.62185469,0.58976916,0.11527176,-3.8901948,-5.78341541,0,29.81758823,19.11865946,17.91334926,-60.75160981,54.50184908,22.55247292,40.47261474,-25.23118763,-10.52823982,-4.84671598,-90.20705215,-180.49362006,20.17737203,0.21458367,0.0172199,0.22917324 +1.76,-29.09995649,-6.04917807,13.10298413,-0.62211397,0.58995485,0.11527253,-3.73028281,-6.01678789,0,29.43914433,19.13196232,17.98360628,-61.31259776,53.92553691,22.97062483,41.71444574,-25.00771402,-10.24279596,-5.02033334,-90.35774916,-180.28525988,20.3726571,0.21872675,0.018809,0.22972991 +1.77,-29.0804837,-6.02001415,13.13372759,-0.62245941,0.59012018,0.11538557,-3.57651504,-6.15161841,0,29.25668632,19.0624835,17.99410623,-61.9087161,53.18420273,23.50696097,43.18745997,-25.17884602,-10.25700498,-5.43666774,-90.57340475,-180.02568815,20.55430445,0.22326206,0.02043678,0.23032606 +1.78,-29.05404058,-5.97384488,13.15445901,-0.62282719,0.59031471,0.11559098,-3.50111511,-6.24604184,0,29.13870139,19.05903377,17.93123234,-62.60267301,52.37882107,24.42794341,44.7388989,-25.90427275,-10.17882977,-6.52132875,-90.79615912,-179.68668504,20.72207899,0.22816159,0.02212122,0.23119961 +1.79,-29.03681176,-5.89612198,13.17436079,-0.62321443,0.59055614,0.11587077,-3.44280375,-6.34635184,0,29.05465669,19.03520582,17.85577795,-63.51067329,51.56362305,25.64473248,46.48712437,-26.64728059,-10.42579642,-6.92411486,-91.04797626,-179.2725067,20.85542848,0.2333663,0.0239049,0.2321596 +1.8,-29.01724091,-5.82619408,13.16141715,-0.6236218,0.59079819,0.11622522,-3.38417084,-6.43257041,0,28.91779451,18.99064364,17.86969773,-64.03603872,50.75744909,26.1967982,48.09835307,-26.79581903,-10.55673343,-7.32648404,-91.32513611,-178.75821144,20.96711672,0.23893013,0.02572412,0.23317454 +1.81,-28.98279584,-5.7757703,13.15748877,-0.62406486,0.59106413,0.1165876,-3.38975681,-6.4841415,0,28.89370124,18.95449074,17.85173662,-64.61555919,49.77967806,27.17828995,49.72299564,-27.23789358,-9.76118597,-8.65381728,-91.62285432,-178.18225278,21.05564843,0.24476049,0.02758563,0.23431179 +1.82,-28.93596086,-5.71975952,13.14893634,-0.62456646,0.59138218,0.11702,-3.22266161,-6.70641927,0,28.32397193,18.85617786,18.03658468,-64.94097406,49.27849452,27.9112719,50.59771908,-24.24244285,-6.6800779,-11.49427654,-91.94396123,-177.55644289,21.12377887,0.25086345,0.02948199,0.2355468 +1.83,-28.86182909,-5.6925384,13.15065654,-0.62505999,0.59170912,0.11737893,-3.24633537,-6.76728725,0,28.55317272,18.84769932,17.81729285,-65.99663686,47.84134655,28.69626272,53.55660806,-27.9671623,-10.29331783,-9.85376969,-92.28030529,-176.86842991,21.16731285,0.25718561,0.03140824,0.23694789 +1.84,-28.83303168,-5.62458578,13.13618196,-0.62558846,0.59203378,0.1179176,-3.14835737,-6.86705703,0,28.27556896,18.77460719,17.9122529,-66.39957117,46.84775294,29.02922835,55.41262053,-27.76355249,-9.84999125,-10.71298889,-92.63409304,-176.14879529,21.19633566,0.26369036,0.03336959,0.2385202 +1.85,-28.79251687,-5.54526684,13.12308792,-0.62608772,0.59239334,0.11848967,-3.04719196,-7.06258236,0,27.95032664,18.75121684,17.93737573,-67.06388602,45.93411094,29.56613851,57.40595718,-27.53378368,-9.72250901,-11.47246827,-92.99477358,-175.41013459,21.22575468,0.27044347,0.03534799,0.2402455 +1.86,-28.75990196,-5.45615332,13.08370677,-0.62657125,0.59274104,0.11916843,-2.98751895,-7.21267219,0,27.76717183,18.73303806,17.93040761,-67.73244926,44.88750475,30.00079424,59.3798377,-26.82796674,-9.50651718,-11.38498408,-93.36432241,-174.66099901,21.2267744,0.27728376,0.03733743,0.24217551 +1.87,-28.69359557,-5.32047141,13.04936961,-0.627029,0.59315078,0.11998532,-2.91070242,-7.39044448,0,27.39820094,18.67630165,17.9936217,-68.16834899,43.99482754,30.43367709,61.37378876,-26.95402684,-9.57413424,-12.62781599,-93.73582716,-173.87306132,21.21525254,0.28422728,0.03935534,0.24427395 +1.88,-28.63016644,-5.21688682,13.00343402,-0.62750662,0.59353428,0.12081309,-2.8474272,-7.5435994,0,27.11867517,18.60566359,18.05990103,-68.54963093,42.92726812,30.66671275,63.47339788,-26.95508612,-9.56988594,-13.22543047,-94.068483,-173.09155295,21.21609797,0.29128288,0.04141573,0.24672463 +1.89,-28.54777679,-5.12183819,12.97060966,-0.6279996,0.59396113,0.12164208,-2.75648603,-7.73538187,0,26.77960586,18.50645832,18.14913756,-68.8860821,41.86524645,30.82279992,65.51989956,-26.46887979,-9.28616232,-14.03597885,-94.41309897,-172.29536092,21.21992457,0.29846316,0.04349619,0.24922144 +1.9,-28.45010083,-5.03583083,12.95403579,-0.62846792,0.59443432,0.12248454,-2.66522131,-7.97041879,0,26.34435802,18.44821079,18.26681574,-69.19915476,40.77400007,30.77911217,67.57999603,-25.18386886,-9.30571268,-14.64099892,-94.76127723,-171.50370587,21.20629748,0.30562235,0.04559742,0.25205921 +1.91,-28.33521173,-4.94496943,12.96716945,-0.62897854,0.59492768,0.12335477,-2.55459686,-8.19823672,0,25.90789721,18.38118456,18.39460639,-69.56007526,39.59695149,30.79425738,69.77929385,-25.10581221,-8.9755776,-14.95368226,-95.07815772,-170.75348038,21.17525164,0.31277054,0.04768134,0.25525124 +1.92,-28.21145058,-4.83095122,12.99146272,-0.6295481,0.5954346,0.12428811,-2.39562064,-8.42479098,0,25.56730239,18.28522363,18.40304493,-70.26472505,38.47124404,31.17762972,71.89545447,-24.49791045,-8.85524811,-15.3216652,-95.41176133,-170.01715217,21.15489669,0.32005407,0.04982439,0.25866932 +1.93,-28.08370691,-4.68774147,13.02637134,-0.63007528,0.59598955,0.12531983,-2.29821782,-8.66898536,0,25.02744634,18.24836032,18.54016206,-70.71671013,37.41391053,31.28590415,74.03655179,-23.94042621,-8.78975667,-15.63945321,-95.77803282,-169.23232166,21.14400495,0.32735281,0.05200902,0.26208389 +1.94,-27.94893293,-4.49441166,13.07637181,-0.63059944,0.59656295,0.12646419,-2.26428343,-8.85063988,0,24.5947666,18.2365913,18.58042407,-71.32581531,36.39381186,31.66928915,76.12389745,-23.71071408,-8.63102046,-16.16675405,-96.14307363,-168.49906891,21.1310114,0.33475137,0.05416514,0.26585548 +1.95,-27.83764538,-4.34305627,13.08598053,-0.63117094,0.59712029,0.12770706,-2.24983682,-9.02116466,0,24.23727304,18.25108944,18.6289649,-71.85307018,35.37673293,32.03657825,78.14321348,-23.56282733,-8.41489532,-16.81304707,-96.50132679,-167.78708871,21.12591868,0.3422359,0.05642335,0.2697657 +1.96,-27.69543105,-4.16697883,13.13319993,-0.63181321,0.59774272,0.12901342,-2.28470822,-9.13968063,0,23.92775702,18.22379622,18.66750854,-72.44312293,34.24918837,32.22551698,80.09264963,-21.94482427,-8.56410762,-16.27866922,-96.84937276,-167.06620688,21.14555593,0.34970191,0.05871996,0.27382117 +1.97,-27.55070666,-4.00640034,13.19000726,-0.63249735,0.59840894,0.13035401,-2.36644509,-9.25669648,0,23.57224424,18.18018518,18.74292261,-72.70220641,33.24480056,32.49492419,82.05392099,-22.11409095,-8.33793351,-17.12168185,-97.17222764,-166.38348134,21.16487136,0.35714558,0.06105145,0.27813422 +1.98,-27.37253672,-3.86653198,13.27860774,-0.63338814,0.5991202,0.1316718,-2.44453021,-9.32678863,0,23.27569889,18.12767809,18.79029119,-72.94336874,32.10516029,32.50792286,83.9205863,-21.50113446,-8.12730955,-17.1428226,-97.49042874,-165.66685783,21.17809044,0.36457779,0.06328886,0.28258522 +1.99,-27.1880352,-3.73247761,13.38824301,-0.63434587,0.59989141,0.13303118,-2.51064507,-9.45849139,0,22.91592934,18.11998838,18.82215911,-73.22477077,30.99881743,32.40173026,85.76535823,-20.40703872,-8.21819381,-16.88765887,-97.83039726,-164.97073936,21.23438502,0.37207925,0.06537683,0.28715467 +2,-26.99300453,-3.59890888,13.50742252,-0.63539647,0.60070426,0.13444049,-2.57347212,-9.5991118,0,22.46479348,18.09858702,18.91444622,-73.24438479,29.96685587,32.1602982,87.54174504,-19.47248116,-8.22183371,-17.54486021,-98.11784545,-164.28292822,21.25193593,0.37926211,0.06789075,0.29186421 +2.01,-26.78403204,-3.43220433,13.63071384,-0.63656335,0.60158202,0.13596513,-2.63719262,-9.74867536,0,22.01861554,18.17459812,18.94865714,-73.44766024,28.91206501,31.8517518,89.34178705,-19.38517061,-7.51249482,-17.25441437,-98.4324291,-163.59225303,21.32075903,0.38650678,0.07026483,0.29664394 +2.02,-26.55749968,-3.24240914,13.72819762,-0.6378465,0.60251399,0.13763864,-2.72990908,-9.89221388,0,21.54033471,18.24414539,19.01654667,-73.41515026,27.98556069,31.40853682,90.98728508,-18.71917922,-7.51592434,-16.90398198,-98.74940874,-162.89262102,21.40093213,0.39367542,0.07261086,0.30143538 +2.03,-26.32243284,-3.0294204,13.802425,-0.63918864,0.60352694,0.13946172,-2.89189486,-10.01528284,0,21.23758676,18.34540773,19.00084525,-73.52662291,27.13934355,31.33245972,92.47758815,-18.00371272,-7.15315769,-17.3337923,-99.05964442,-162.20350555,21.52567999,0.40070849,0.0750402,0.30629404 +2.04,-26.05597207,-2.82120479,13.86736167,-0.64071198,0.6046205,0.14136343,-3.05232414,-10.11667094,0,20.87909044,18.40377546,19.07673274,-73.25868162,26.25368669,30.84570791,93.92945959,-17.27156996,-7.17831845,-17.25075315,-99.43809354,-161.41895909,21.67549943,0.40760415,0.07736235,0.31083986 +2.05,-25.76749708,-2.63042547,13.92981374,-0.6423957,0.60575017,0.14333785,-3.19294905,-10.17638755,0,20.66121623,18.39055724,19.08985339,-73.05700845,25.31648188,30.42922181,95.31896764,-16.4749161,-6.95834448,-16.89064966,-99.76221549,-160.64754582,21.79926323,0.41445428,0.07963692,0.31553396 +2.06,-25.45301194,-2.47371874,14.00991438,-0.64423298,0.60696546,0.14530481,-3.32198411,-10.26453965,0,20.40261062,18.37389445,19.13487967,-72.66130818,24.39739791,29.8218428,96.58496632,-15.55982303,-6.64042127,-16.60797177,-100.06590732,-159.88907471,21.96322875,0.42091186,0.08191422,0.32024269 +2.07,-25.11210723,-2.32240883,14.10617943,-0.646232,0.6082661,0.14729139,-3.3991881,-10.39225258,0,20.03522579,18.34350815,19.17709881,-72.09864837,23.56545573,28.99230489,97.82655213,-14.78735866,-6.5234957,-16.30023659,-100.35525761,-159.09428223,22.14283692,0.42728741,0.08418669,0.32479446 +2.08,-24.74757006,-2.17936134,14.21905823,-0.64835925,0.60960665,0.14929787,-3.4791824,-10.51876117,0,19.70011733,18.35427319,19.19140987,-71.6770007,22.74611879,28.31733886,99.02136423,-14.0679941,-6.29143917,-16.25151797,-100.63158559,-158.25561874,22.28526672,0.43337145,0.08659925,0.32933405 +2.09,-24.3641977,-2.04035006,14.32815606,-0.65059953,0.61097486,0.15133992,-3.52843249,-10.67415486,0,19.341337,18.3935668,19.21477997,-71.19332872,21.93389169,27.47257353,100.13807824,-12.83383107,-6.23869776,-15.79371343,-100.96346966,-157.52856953,22.43063608,0.43967312,0.08869841,0.33368537 +2.1,-23.97715029,-1.89543366,14.42627294,-0.65292527,0.61232954,0.15342074,-3.58583141,-10.81604079,0,19.04587255,18.44155531,19.21653986,-70.76052389,21.15654189,26.75837859,101.23906002,-12.19811234,-5.99559691,-15.80675748,-101.29654287,-156.72516548,22.54693821,0.44577449,0.09097141,0.33788672 +2.11,-23.59528588,-1.74281013,14.51833481,-0.65529013,0.6136449,0.15549515,-3.67711307,-10.94414992,0,18.77621599,18.49596976,19.2223575,-70.28431707,20.45068465,26.0576659,102.24348868,-11.37628151,-5.89501318,-15.71190789,-101.64361619,-155.96008632,22.7431656,0.45142255,0.09315137,0.3420553 +2.12,-23.22878358,-1.60084836,14.59849587,-0.65772849,0.61486689,0.15755009,-3.77337619,-11.06104802,0,18.55797842,18.49431062,19.20279705,-69.88713107,19.82047065,25.50674863,103.20685916,-10.6312225,-5.80366347,-15.66413824,-101.98309142,-155.15221413,22.91694412,0.45701592,0.09539659,0.34628764 +2.13,-22.86024104,-1.46629808,14.67309314,-0.66016934,0.6160329,0.15955076,-3.8658076,-11.1736102,0,18.30751465,18.50236461,19.21014219,-69.33438959,19.21307646,24.77730403,104.16107193,-10.11123616,-5.69348145,-15.58201607,-102.35332812,-154.35135514,23.10597262,0.46245613,0.09763144,0.35050039 +2.14,-22.50043143,-1.32709166,14.75014915,-0.66261243,0.61715058,0.1615217,-3.94425633,-11.30251365,0,18.01547337,18.49032386,19.22104759,-68.84394828,18.70046067,24.16148387,105.08159563,-9.52841251,-5.45740074,-15.79182517,-102.74358895,-153.57097163,23.33495417,0.46758174,0.09981352,0.35471406 +2.15,-22.13696006,-1.19538722,14.82555758,-0.66504772,0.61822812,0.1634096,-3.99594788,-11.41392277,0,17.74824016,18.4680368,19.23008927,-68.38101074,18.16872346,23.50714632,106.02080279,-8.83060338,-5.70030149,-15.581799,-103.12623654,-152.78833626,23.52459431,0.47264363,0.10206503,0.35899009 +2.16,-21.77462362,-1.0363285,14.91628215,-0.66743329,0.61926119,0.16523541,-4.045608,-11.48531786,0,17.49212135,18.43143463,19.22653717,-68.02081927,17.65583207,23.02809558,106.94195361,-8.23570097,-5.55187955,-15.87288947,-103.54283544,-152.01792996,23.74151909,0.47742795,0.10449302,0.36338304 +2.17,-21.44203845,-0.9047504,14.9655875,-0.66971404,0.62019254,0.1669752,-4.06107081,-11.56481847,0,17.36360477,18.37853604,19.18032084,-68.06418337,17.14808677,22.96697269,107.8684022,-7.7271021,-5.48308091,-16.1839502,-104.06382282,-151.3565554,24.06108011,0.48223818,0.10676819,0.36776378 +2.18,-21.12653964,-0.78493166,15.02574832,-0.67183939,0.62105365,0.16855609,-4.09419017,-11.65371929,0,17.07398729,18.36944465,19.25136816,-67.62087992,16.67626014,22.41345131,108.79729636,-7.34057918,-5.39972446,-16.52677655,-104.72591064,-150.68345151,24.44516168,0.48692614,0.10915823,0.37248515 +2.19,-20.82444941,-0.68099913,15.04902212,-0.6738969,0.62182634,0.17006129,-4.05057106,-11.70635333,0,16.97664622,18.29000401,19.23623289,-67.69961717,16.13177496,22.3168076,109.75188518,-6.99786906,-5.41702311,-16.65436829,-105.22244327,-150.02050641,24.69775293,0.4917646,0.11191652,0.37716674 +2.2,-20.56496257,-0.58396175,15.1322862,-0.67573402,0.62253058,0.17139209,-4.0861395,-11.77423536,0,16.84821232,18.2337466,19.25186501,-67.81015312,15.59696401,22.18553882,110.72600954,-6.66308295,-5.22011187,-16.7546778,-105.77081223,-149.34860236,24.98798205,0.49623217,0.114537,0.38214689 +2.21,-20.3083668,-0.49519943,15.24034519,-0.67753102,0.62317167,0.17260379,-4.07374224,-11.72792502,0,16.77430561,18.10351506,19.30766391,-67.60057958,14.89120609,21.70583407,111.7431417,-6.12863811,-5.15914467,-16.65805591,-106.38891476,-148.68305891,25.32165427,0.50062203,0.11724893,0.38726621 +2.22,-20.0772349,-0.40877051,15.39787342,-0.67916912,0.62376455,0.17370928,-4.05565955,-11.77844087,0,16.58458709,18.02084291,19.29479898,-67.81759616,14.36028233,21.46570481,112.81243853,-6.03138463,-5.07851794,-16.58783861,-106.99659982,-148.08254277,25.64055129,0.50498496,0.12003497,0.39270744 +2.23,-19.86516902,-0.33692847,15.58944214,-0.6807365,0.6243315,0.17472962,-4.03400099,-11.80306377,0,16.29442646,17.96501027,19.35464049,-67.35991067,13.82766026,20.49503372,113.8840849,-5.90403189,-4.96176995,-16.53582339,-107.64346647,-147.46770122,26.00726875,0.50928559,0.12291653,0.3981798 +2.24,-19.66337059,-0.26790873,15.81039876,-0.68225773,0.62488685,0.17568178,-4.07590107,-11.69511456,0,16.21461465,17.93473053,19.38448944,-66.59262287,13.08801006,19.11551239,114.8979409,-5.71539654,-4.55126346,-16.11808928,-108.2640364,-146.81980947,26.35167965,0.51339633,0.12571035,0.40383216 +2.25,-19.49561634,-0.20717482,16.05078581,-0.68372016,0.62539625,0.17661085,-4.16765123,-11.62691072,0,16.06894894,17.97640124,19.42116673,-65.67091322,12.51503921,17.52343581,115.92375787,-5.43708923,-4.38514903,-15.93702981,-108.92003705,-146.16735588,26.73010673,0.51733627,0.12850269,0.40955294 +2.26,-19.33253365,-0.16051307,16.27802856,-0.68527696,0.6259609,0.17755408,-4.23924894,-11.60215852,0,15.91359675,17.99527109,19.45515406,-64.71328787,12.05766136,15.88407151,116.90575433,-5.11530141,-4.26981091,-15.73267238,-109.622606,-145.4737848,27.18449137,0.52098717,0.13142679,0.41530654 +2.27,-19.11740318,-0.10745664,16.49893162,-0.68689438,0.62653276,0.17847926,-4.32442776,-11.5234956,0,15.82487269,17.98980847,19.46104857,-63.86998764,11.60942856,14.44333038,117.86169278,-4.85454123,-4.15956635,-15.6447355,-110.28941641,-144.72269154,27.58218722,0.52471727,0.13411674,0.42109787 +2.28,-18.98678365,-0.0701347,16.72690781,-0.68845239,0.62699954,0.17935984,-4.51527068,-11.40470767,0,15.82926925,18.05280701,19.49902327,-62.62036712,11.22450627,12.52813501,118.75087901,-4.50646411,-4.08558831,-15.37920022,-111.02764862,-143.94200939,28.05632691,0.52810131,0.13679731,0.42702933 +2.29,-18.79025635,-0.02821174,16.93394803,-0.69015497,0.62755586,0.18025616,-4.6561694,-11.32201471,0,15.80881081,18.07896405,19.49289291,-61.67428128,10.94248639,10.96868815,119.60011901,-4.42982932,-3.82880775,-15.29157477,-111.79297093,-143.11149069,28.54564172,0.53134351,0.1394583,0.4329662 +2.3,-18.55419657,0.01409037,17.10031368,-0.6919745,0.6281776,0.18118231,-4.7114088,-11.25606754,0,15.9284609,18.00989295,19.32912078,-61.93767741,10.73361124,10.60372944,120.40780639,-4.09553961,-3.72686126,-15.24416407,-112.60744177,-142.23321486,29.10150344,0.53423077,0.14203882,0.43884395 +2.31,-18.30344242,0.04601201,17.25064059,-0.69383575,0.62879914,0.18206695,-4.81195783,-11.18883539,0,15.74066985,18.07444087,19.47458471,-59.77935391,10.61883812,7.85851519,121.14714618,-3.84282021,-3.59268607,-15.13617586,-113.44838906,-141.30535072,29.67995215,0.53701605,0.14453415,0.44478632 +2.32,-18.04121478,0.10184941,17.37549679,-0.6957342,0.62942832,0.1829989,-4.93285773,-11.07669957,0,15.84908872,18.08700559,19.41826887,-59.19970637,10.52664019,6.73082636,121.83467521,-3.55820303,-3.49175947,-15.18666676,-114.34792375,-140.31887414,30.31143296,0.5396253,0.14695035,0.45074512 +2.33,-17.71813384,0.14016618,17.44646957,-0.69771799,0.63023488,0.18394894,-4.99107974,-11.05337726,0,15.9351625,18.06535175,19.40146112,-58.80297332,10.46495827,5.83139698,122.49713379,-3.43446149,-3.4959161,-15.13084074,-115.29532396,-139.30200296,30.96816247,0.54212878,0.14926477,0.45668642 +2.34,-17.40199777,0.22234848,17.49400398,-0.69973118,0.63095861,0.18498189,-5.03760088,-11.03669172,0,15.95196718,18.04480943,19.34238829,-59.02879485,10.6723422,5.5753568,123.16925936,-3.5145756,-3.77997403,-15.27876517,-116.30059177,-138.2527236,31.69692101,0.54435331,0.15149466,0.46272686 +2.35,-17.03554065,0.27955944,17.52299787,-0.70173103,0.63176336,0.18594405,-5.14573262,-11.01758775,0,15.99119852,18.0973454,19.35248286,-58.37356406,10.79395779,4.48315088,123.74160545,-3.30773933,-3.82983339,-15.39653487,-117.36554087,-137.16721997,32.44088915,0.54643305,0.15351982,0.4687758 +2.36,-16.67630045,0.34500199,17.51782157,-0.70382156,0.63252869,0.18693897,-5.23911885,-10.88967676,0,16.22490377,18.07605126,19.31911855,-57.63295742,10.84973257,3.32749425,124.24385557,-3.23862468,-3.7032982,-15.23855562,-118.40357451,-136.10181152,33.19362336,0.54842342,0.15546501,0.47497103 +2.37,-16.2773092,0.4177265,17.49476907,-0.70593172,0.6333376,0.1879315,-5.29266146,-10.89684686,0,16.29041148,18.07257173,19.29238305,-57.82565614,11.15982258,3.13448718,124.75284433,-3.28220106,-3.84967459,-15.42238438,-119.53467886,-134.93642153,34.04160246,0.55016099,0.15736918,0.48095058 +2.38,-15.85567715,0.49804374,17.47538938,-0.70798421,0.63410742,0.18890021,-5.40606339,-10.81459023,0,16.56220805,18.07917886,19.18302378,-57.98551205,11.34872322,2.89123679,125.23588522,-3.34546827,-3.9144495,-15.4137373,-120.71240421,-133.80985596,34.91355435,0.55179519,0.15909933,0.48708752 +2.39,-15.44565278,0.57772473,17.43808581,-0.71012839,0.63493517,0.18989661,-5.43618167,-10.81802123,0,16.66801394,18.08086708,19.16241376,-58.1586937,11.71988276,2.66330898,125.72868432,-3.44471785,-4.15567423,-15.61501995,-121.92062304,-132.67603035,35.81389262,0.5533595,0.16068986,0.49327734 +2.4,-15.02807952,0.66026446,17.40167229,-0.7122711,0.6357436,0.19086935,-5.47793374,-10.73291358,0,16.81811155,18.07026394,19.21488913,-57.44992272,12.00536358,1.58343795,126.2190314,-3.52395205,-4.3971126,-15.6333357,-123.07349636,-131.54960181,36.6908972,0.55487792,0.16216295,0.49968061 +2.41,-14.59671699,0.77385065,17.3731318,-0.7143845,0.63654909,0.19186061,-5.54553427,-10.64771926,0,17.06765136,18.07325392,19.1409847,-57.63152631,12.34763254,1.33594978,126.71217306,-3.58860861,-4.76148851,-15.62449053,-124.23070385,-130.51983482,37.56618857,0.55638392,0.1635462,0.50618771 +2.42,-14.15129512,0.8734384,17.3312128,-0.71649865,0.63736064,0.19282455,-5.62081711,-10.55855149,0,17.35415583,18.08359099,19.07850308,-57.77877158,12.70050783,1.09718818,127.20193123,-3.79753033,-5.13312127,-15.70167493,-125.437226,-129.43637864,38.50449031,0.55761302,0.16471479,0.51268102 +2.43,-13.66941025,0.97967497,17.3194397,-0.71867381,0.63817482,0.19376166,-5.71303307,-10.47834272,0,17.48349741,18.12878418,19.07679396,-57.48647557,13.18207316,0.50758517,127.67898906,-3.56217066,-5.6750653,-16.13949066,-126.59096065,-128.41172992,39.414322,0.55881608,0.16577163,0.51932388 diff --git a/tests/data/is_expected_array_val.csv b/tests/data/is_expected_array_val.csv new file mode 100644 index 0000000..10efe63 --- /dev/null +++ b/tests/data/is_expected_array_val.csv @@ -0,0 +1,68 @@ +idx,shape_val,first_last_val,mean_val,median_val,sum_val,nans_val +1,"(4, 11600)","(2.6089122911798768e-05, 0.0)",4.89E-05,2.53E-05,2.26749462,0 +2,"(4, 4, 580)","(744.5535888671875, 1.0)",385.66622389,278.73994446,3537330.60548401,108 +3,"(4, 4, 580)","(744.5535888671875, 1.0)",385.66622389,278.73994446,3537330.60548401,108 +4,"(4, 11600)","(2.6089122911798768e-05, 0.0)",4.89E-05,2.53E-05,2.26749462,0 +5,"(4, 4, 580)","(7.227399568656779, 0.0)",1.01E-14,0,9.3E-11,108 +6,"(4, 4, 580)","(0.0, 0.0)",-4.99871984,0,-45848.25834656,108 +7,"(4, 11600)","(-6.7067797316499e-06, 0.0)",2.74E-22,0,1.27E-17,0 +8,"(4, 11600)","(-2.000026089122912, -2.0)",-1.99999262,-1.99999841,-92799.65768644,0 +9,"(4, 11600)","(0.0, 0.0)",1.14E-06,0,0.05304382,0 +10,"(38, 11600)","[-0.022051572799682617, 0.0]",-0.00167903,0,-740.11800894,0 +11,"(4, 11600)","[-2.6089122911798768e-05, 0.0]",7.38E-06,1.59E-06,0.34231356,0 +12,"(4, 11600)","[-0.01039797067642212, 1.0517047485336661e-05]",-0.00743816,-2.68E-05,-345.13052138,0 +13,"(1, 11600)","[-2.6089122911798768e-05, -1.912586776597891e-05]",-1.94E-05,-2.15E-05,-0.22483518,0 +14,"(1, 11600)","[-0.01544412225484848, 0.0]",-0.08497782,-0.01491006,-985.74269734,0 +15,"(4, 51, 580)","[44.16278839111328, 1.0]",362.29798491,337.75192261,42535594.9182787,915 +16,"(4, 4, 580)","[744.5535888671875, 1.0]",385.66622389,278.73994446,3537330.60548401,108 +17,"(4, 4, 580)","[32.572296142578125, 1.0]",249.52316566,87.43008041,2309586.42132697,24 +18,"(4, 1, 580)","[744.5535888671875, 1.0]",422.10368959,312.92277527,976747.93771362,6 +19,"(4, 1, 580)","[-93.72181701660156, 1.0]",242.87715054,124.81892014,556188.67473757,30 +20,"(4, 4, 580)","(93.67261197255544, 100.0)",94.22964323,95.75328141,864274.28769951,108 +21,"(4, 4, 580)","(0.9367261197255545, 1.0)",0.94229643,0.95753281,8642.742877,108 +22,"(4, 4, 580)","(99.78350514873623, 100.0)",98.77431962,100,905958.05954149,108 +23,"(4, 11600)","(-1.3214177393296322, np.nan)",16.72098993,5.42284695,581890.44957574,11600 +24,"(4, 11600)","(-0.013214177393296323, np.nan)",0.1672099,0.05422847,5818.90449576,11600 +25,"(4, 11600)","(109.2569491430397, np.nan)",91.45703323,96.68573809,3182704.75640438,11600 +26,"(4, 4, 100)","(744.5535888671875, 1.0)",385.12492092,278.03732577,606956.8753713,24 +27,"(4, 4, 1000)","(744.5535888671875, 1.0)",385.47871377,278.42480588,6089021.76272354,204 +28,"(4, 4, 100)","(744.5535888671875, 1.0)",391.29200149,303.40001023,626067.2023861,0 +29,"(4, 100)","(-2.6089122911798768e-05, 0.0)",5.38E-06,1.08E-05,0.00215289,0 +30,"(4, 1000)","(-2.6089122911798768e-05, 0.0)",6.84E-06,1.21E-06,0.02736488,0 +31,"(4, 100)","(-2.6089122911798768e-05, 0.0)",7.35E-06,7.36E-06,0.00293887,0 +32,"(4, 11600)","(-2.304462465507838e-05, 0.0)",7.38E-06,8.38E-06,0.342223358074015,0 +33,"(4, 11600)","(2.5595935255889553e-07, 0.0)",-4.01E-10,0,-1.86E-05,0 +34,"(4, 11600)","(3.2672989824212777e-07, 0.0)",-9.51E-11,0,-4.41E-06,0 +35,"(4, 11600)","(-2.5446843700691505e-05, 0.0)",7.38E-06,4.06E-06,0.342335416199246,0 +36,"(4, 4, 580)","(np.nan, 0.9999999999999998)",183.562281646007,1,745262.863482787,5220 +37,"(4, 4, 580)","(np.nan, 0.0)",-2.4E-05,0,-0.097574170514675,5220 +38,"(4, 4, 580)","(np.nan, -1.3866695599588098e-31)",0.001586958239911,-6.01E-32,6.44305045403796,5220 +39,"(4, 4, 580)","(np.nan, 1.0000000000000082)",183.562215983458,1.0000000000002,745262.59689284,5220 +40,"(4, 5800)","(3.8764686360297736e-05, 0.0)",6.92E-07,3.89E-08,0.016056162554059,0 +41,"(4, 11600)","(1.9382343180148868e-05, 0.0)",3.45E-07,1.95E-08,0.015987901541218,0 +42,"(4, 4, 290)","(np.nan, 4.13924084623312e-19)",1.33720390390587,2.52E-17,2714.52392492892,2610 +43,"(4, 4, 580)","(np.nan, 3.820890352350837e-17)",0.352121599861264,1.26E-17,1429.61369543673,5220 +44,"(4, 580)","(1038.6935415863582, 1038.8700310469928)",993.242627006278,1006.39751655676,2268566.16008234,36 +45,"(3, 580)","(1556.51305053091, 1143.8182846795867)",1056.40879392732,1170.61615870489,1730397.60445295,102 +46,"(3, 4)","(np.nan, np.nan)",10291.7582539651,5506.3059770213,30875.2747618952,9 +47,"(11600,)","(4.4752859722131086e-05, 3.742301697423383e-05)",0.000146185827734,7.41E-05,1.69575560171616,0 +48,"(4,)","(0.02294474090646737, 0.0)",0.008432423013524,0.005392475573815,0.033729692054098,0 +49,"(1, 2)","(222, 312)",267,267,534,0 +50,"(1, 2)","(195, 331)",263,263,526,0 +51,"(4, 4, 580)","(False, False)",0,0,0,0 +52,"(4, 4, 580)","(False, False)",0.27230603448275864,0,2527,0 +53,"(4, 11600)","(False, False)",0.01870689655172414,0,868,0 +54,"(4, 11600)","(False, False)",0.13142241379310346,0,6089,0 +55,"(100,)","(0.0, 99.0)",49.5,49.5,4950,0 +56,"(4, 11600)","(-2.6089122911798768e-05, 0.0)",7.38E-06,1.59E-06,0.342313555106536,0 +57,"(4, 4, 580)","(744.5535888671875, 1.0)",385.666223886176,278.739944458008,3537330.60548401,108 +58,"(4, 51, 580)","(44.1628, 1.0)",362.298001357552,337.752,42535596.8493834,915 +59,"(38, 11600)","(-0.022051599999999998, 0.0)",-0.001679033559866,0,-740.117993188906,0 +60,"(4, 2, 580)","(44.1628, 1.0)",183.787047406121,1,846706.9274,33 +61,"(2, 994)","(-0.0220516, -0.0106269)",-0.015581109305835,-0.0151948,-30.9752453,0 +62,"(25, 92)","(-4.47756531, -3.32306483)",9.20241576308695,-0.000852665,21165.5562551,0 +63,"(25, 88)","(-27.90807185, 0.51932388)",-6.77368899892727,0.11518549,-14902.11579764,0 +64,"(4, 51, 93)","(733.7041, 1.0)",319.803440918195,303.8114,6067310.8811,0 +65,"(4, 51, 580)","(44.16278839111328, 1.0)",362.29798490932,337.751922607422,42535594.9182787,915 +66,"(1, 38, 11600)","(-0.022051572799682617, 0.0)",-0.001679033595597,0,-740.118008939112,0 +67,"(4, 4, 580)","(744.5535888671875, 1.0)",385.4765160798091,278.7399444580078,3535590.605484009,108 diff --git a/tests/data/markers.trc b/tests/data/markers.trc new file mode 100644 index 0000000..5528c5f --- /dev/null +++ b/tests/data/markers.trc @@ -0,0 +1,99 @@ +PathFileType,4,(X/Y/Z),/home/romain/Downloads/irsst/dapo/0_markers/DapOF6H1_1.trc,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +DataRate,CameraRate,NumFrames,NumMarkers,Units,OrigDataRate,OrigDataStartFrame,OrigNumFrames,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +100,100,652,51,mm,100,0,652,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +Frame#,Time,ASISl,,,ASISr,,,PSISl,,,PSISr,,,STER,,,STERl,,,STERr,,,T1,,,T10,,,XIPH,,,CLAVm,,,CLAVl,,,CLAV_ant,,,CLAV_post,,,CLAV_SC,,,ACRO_tip,,,SCAP_AA,,,SCAPl,,,SCAPm,,,SCAP_CP,,,SCAP_RS,,,SCAP_SA,,,SCAP_IA,,,CLAV_AC,,,DELT,,,ARMl,,,ARMm,,,ARMp_up,,,ARMp_do,,,EPICl,,,EPICm,,,LARMm,,,LARMl,,,LARM_elb,,,LARM_ant,,,STYLr,,,STYLr_up,,,STYLu,,,WRIST,,,INDEX,,,LASTC,,,MEDH,,,LATH,,,boite_gauche_ext,,,boite_gauche_int,,,boite_droite_int,,,boite_droite_ext,,,boite_avant_gauche,,,boite_avant_droit,,,boite_arriere_droit,,,boite_arriere_gauche,, +,,X1,Y1,Z1,X2,Y2,Z2,X3,Y3,Z3,X4,Y4,Z4,X5,Y5,Z5,X6,Y6,Z6,X7,Y7,Z7,X8,Y8,Z8,X9,Y9,Z9,X10,Y10,Z10,X11,Y11,Z11,X12,Y12,Z12,X13,Y13,Z13,X14,Y14,Z14,X15,Y15,Z15,X16,Y16,Z16,X17,Y17,Z17,X18,Y18,Z18,X19,Y19,Z19,X20,Y20,Z20,X21,Y21,Z21,X22,Y22,Z22,X23,Y23,Z23,X24,Y24,Z24,X25,Y25,Z25,X26,Y26,Z26,X27,Y27,Z27,X28,Y28,Z28,X29,Y29,Z29,X30,Y30,Z30,X31,Y31,Z31,X32,Y32,Z32,X33,Y33,Z33,X34,Y34,Z34,X35,Y35,Z35,X36,Y36,Z36,X37,Y37,Z37,X38,Y38,Z38,X39,Y39,Z39,X40,Y40,Z40,X41,Y41,Z41,X42,Y42,Z42,X43,Y43,Z43,X44,Y44,Z44,X45,Y45,Z45,X46,Y46,Z46,X47,Y47,Z47,X48,Y48,Z48,X49,Y49,Z49,X50,Y50,Z50,X51,Y51,Z51 +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +1,0,733.7041,80.9832,191.8668,651.6759,313.4902,178.9877,873.158,201.7161,219.6408,838.995,307.5481,218.5691,699.7365,222.8357,618.6591,710.3171,191.4298,615.6643,699.7365,222.8357,618.6591,859.903,261.0599,614.3923,873.315,257.5624,480.5657,677.7758,192.597,429.3874,695.4249,276.592,638.2006,710.2213,340.6371,645.5005,691.5009,302.2924,619.0906,717.7944,313.8936,651.8928,702.5284,247.032,639.2047,689.2977,401.6461,622.4409,721.3292,414.6639,620.8755,759.51,396.028,630.3092,798.8601,372.5215,635.8339,685.9903,334.8158,607.3627,834.381,337.1425,625.8981,818.1131,344.7588,649.9964,851.7479,356.433,503.5621,697.7493,368.8945,639.321,675.3735,422.6158,468.8659,706.922,446.2391,443.6955,696.074,364.4242,371.6159,740.4147,435.6993,411.4393,740.3168,426.2383,354.8723,710.9666,443.1654,302.0166,719.6227,377.6075,284.6596,672.8967,464.0937,210.5655,635.8495,402.0233,203.0854,723.0645,426.626,257.4451,614.7819,451.8124,156.7794,589.3965,469.5879,72.3312,612.3084,474.6541,96.0126,634.8295,453.5639,49.5676,640.0541,460.5585,83.9149,597.3163,487.1243,-5.6732,620.5352,451.6007,-13.8173,626.0219,455.174,18.6501,606.382,478.1108,26.6882,65.9025,-256.7239,179.7892,54.746,-223.0001,179.5193,-71.0396,145.2855,178.243,-83.213,178.3283,178.1282,64.2318,-171.1209,225.9194,-32.5313,110.5802,226.2722,249.3289,208.0386,229.3641,346.7747,-75.9999,229.859 +2,0.01,733.4084,81.0119,191.8306,651.5213,313.561,179.0034,872.9325,201.6858,219.63,838.8142,307.5489,218.5649,699.8896,222.826,618.6633,710.4393,191.3984,615.6716,699.8896,222.826,618.6633,860.0472,260.8163,614.2618,873.3508,257.3082,480.4645,677.6908,192.561,429.4388,695.6576,276.5903,638.1923,710.5372,340.6252,645.4835,691.7862,302.2936,619.0763,718.0958,313.8694,651.877,702.7236,247.0154,639.2014,689.8046,401.6558,622.3357,721.7991,414.5777,620.7665,759.908,395.9305,630.2158,799.2618,372.3409,635.7151,686.2802,334.8327,607.3523,834.6656,336.9195,625.7724,818.4454,344.565,649.8882,851.933,356.1562,503.4382,698.1136,368.9025,639.3031,675.5565,422.9812,468.9893,707.2322,446.4392,443.7583,693.9752,363.9149,371.5729,740.5598,435.6911,411.3883,740.2364,426.2969,354.8039,710.8539,443.6061,302.0843,717.978,378.0453,284.4272,672.8337,465.1891,210.8336,634.9897,403.6064,203.3285,722.6551,427.0386,257.4777,614.5073,453.88,157.1539,589.3093,472.2192,72.83,612.3213,476.8236,96.4088,634.2787,455.321,49.8575,639.7495,462.2192,84.1773,597.3807,489.8861,-5.0428,620.2345,453.7824,-13.5444,624.8866,457.2231,19.0096,606.3381,480.5287,27.2234,65.8941,-256.7207,179.7988,54.7425,-223.0006,179.5261,-71.0396,145.2855,178.243,-83.213,178.3283,178.1282,64.229,-171.1216,225.9214,-32.5126,110.4148,226.0035,249.3286,208.0353,229.367,346.7743,-76.0044,229.8591 +3,0.02,733.0995,81.0535,191.795,651.3495,313.6268,179.0038,872.6972,201.6753,219.611,838.6234,307.5551,218.5601,700.035,222.7969,618.6691,710.5559,191.3611,615.6837,700.035,222.7969,618.6691,860.192,260.5775,614.1511,873.368,257.0413,480.3578,677.6068,192.5171,429.481,695.8809,276.5795,638.1763,710.8563,340.6046,645.4449,692.0469,302.3035,619.066,718.3946,313.8336,651.8418,702.9116,247.0054,639.2028,690.2115,401.6682,622.3181,722.2024,414.5621,620.7523,760.2803,395.8208,630.1274,799.5974,372.1805,635.5996,686.5693,334.85,607.3293,834.9323,336.6979,625.6418,818.7625,344.3718,649.7542,852.1061,355.89,503.3019,698.4803,368.8983,639.2764,675.756,423.3743,469.0909,707.4915,446.8699,444.0154,694.6537,365.0069,371.576,740.7479,435.6786,411.3294,740.2266,426.5359,354.7393,710.798,444.0765,302.1592,717.1454,378.4754,284.3279,672.7953,466.251,211.0814,634.1929,405.2033,203.5604,722.2965,427.465,257.4774,614.2549,455.8132,157.5085,589.2079,474.8212,73.3514,612.1777,479.0957,96.8599,633.6931,457.019,50.1665,639.4156,463.8335,84.4603,597.4312,492.5468,-4.4043,619.0864,455.9207,-13.1699,625.0562,458.8972,19.4505,606.2772,482.8821,27.7541,65.9009,-256.7221,179.7988,54.7442,-222.9995,179.5266,-71.0457,145.2917,178.2459,-83.2095,178.33,178.1261,64.2318,-171.1209,225.9194,-32.507,110.4146,226.0089,249.3311,208.0364,229.3688,346.7749,-76.0198,229.8624 +4,0.03,732.7933,81.0875,191.7625,651.1667,313.7065,179.0085,872.4538,201.6676,219.6051,838.4225,307.5699,218.5626,700.1769,222.7726,618.678,710.6682,191.3186,615.689,700.1769,222.7726,618.678,860.3289,260.3434,614.0283,873.3785,256.8095,480.2549,677.5334,192.4396,429.5162,696.0894,276.5718,638.1614,711.14,340.5756,645.4163,692.2858,302.2993,619.0524,718.6808,313.8084,651.8054,703.0878,246.9728,639.197,690.5974,401.6835,622.2902,722.6447,414.4695,620.6283,760.6637,395.7077,630.0104,799.9417,372.018,635.4831,686.8359,334.873,607.3061,835.3536,336.5964,625.58,819.07,344.1864,649.6398,852.2783,355.6182,503.1661,698.8157,368.8943,639.2365,675.8377,423.9748,469.2239,707.8612,447.1911,444.0151,694.0232,365.331,371.5385,740.9365,435.6768,411.2817,740.1462,426.4663,354.6405,710.7739,444.5296,302.2101,716.3181,378.912,284.2296,672.7716,467.3122,211.312,633.4,406.7701,203.8155,721.9507,427.899,257.459,614.0146,457.7525,157.8993,589.0762,477.3772,73.8957,612.3017,481.0829,97.2224,633.066,458.6781,50.488,639.0486,465.414,84.7557,597.2802,495.065,-3.8653,618.4789,458.2634,-13.0138,624.4637,460.7426,19.8648,606.1602,485.1716,28.3319,65.8896,-256.7182,179.8002,54.7271,-223.0006,179.5322,-71.0457,145.2917,178.2459,-83.2075,178.3295,178.1303,64.2313,-171.1222,225.9223,-32.5177,110.4377,226.0576,249.3285,208.0329,229.3617,346.774,-76.0364,229.8652 +5,0.04,732.4724,81.1216,191.7307,650.9807,313.7824,179.0002,872.2025,201.6697,219.5882,838.2,307.5927,218.5459,700.3022,222.7364,618.6811,710.7663,191.2706,615.6996,700.3022,222.7364,618.6811,860.4344,260.1249,613.9037,873.3814,256.5801,480.1463,677.4464,192.3775,429.5649,696.29,276.5604,638.1519,711.4224,340.5511,645.3713,692.5339,302.3115,619.0321,718.9424,313.7738,651.7633,703.2556,246.9508,639.1927,690.9858,401.6872,622.2676,723.0049,414.4593,620.5959,761.0087,395.6104,629.9088,800.2607,371.8562,635.3655,687.08,334.8882,607.2765,835.594,336.3855,625.4363,819.3604,344.0036,649.5114,852.4303,355.3607,503.0337,699.1395,368.9012,639.2087,676.0869,424.331,469.2866,708.2744,447.3518,444.0368,692.4562,365.1151,371.4604,741.1561,435.6674,411.2274,740.1443,426.5493,354.5668,710.8307,444.9655,302.2515,714.2026,379.4377,283.9732,672.791,468.3513,211.5389,632.6176,408.3428,204.0994,721.6536,428.3012,257.4247,613.7817,459.6823,158.3005,588.9442,479.8677,74.4641,612.1401,483.2465,97.7071,632.382,460.3005,50.8358,638.6753,466.9446,85.0332,597.2053,497.5825,-3.1657,617.6218,460.4314,-12.5388,623.7608,462.5863,20.3076,606.0023,487.4093,28.9142,65.888,-256.722,179.7906,54.7366,-222.9995,179.5335,-71.0432,145.2934,178.2428,-83.2056,178.3266,178.1289,64.23,-171.1268,225.924,-32.5471,110.6193,226.344,249.3261,208.0323,229.3649,346.7758,-76.0561,229.8714 +6,0.05,732.1597,81.1709,191.6971,650.7913,313.8546,178.9813,871.9352,201.6796,219.6029,837.9594,307.6246,218.5516,700.4246,222.7169,618.6815,710.8587,191.2292,615.7053,700.4246,222.7169,618.6815,860.5557,259.9113,613.7816,873.3582,256.3513,480.0338,677.372,192.3075,429.5949,696.4753,276.5574,638.1259,711.7042,340.5175,645.3259,692.7745,302.3067,619.0052,719.1987,313.7411,651.7158,703.42,246.9289,639.1825,691.3466,401.6983,622.2343,723.4015,414.4056,620.5212,761.3575,395.5101,629.8062,800.5428,371.7105,635.2488,687.3196,334.8993,607.244,835.9177,336.1502,625.4241,819.6292,343.8359,649.3887,852.5585,355.1191,502.9089,699.4304,368.8828,639.1743,676.3404,424.7047,469.3687,708.7111,447.3877,444.0685,692.0246,365.5812,371.4124,741.4152,435.6444,411.188,740.1769,426.6387,354.4901,710.8882,445.4059,302.2976,713.5495,379.8605,283.8801,672.8335,469.3554,211.7545,631.8636,409.9211,204.3954,721.3677,428.7035,257.3874,613.5436,461.584,158.7465,588.8093,482.6624,75.0462,612.2423,485.1337,98.0848,631.611,461.8458,51.1766,638.2458,468.4347,85.3413,597.1047,500.0415,-2.4578,616.6864,462.5198,-12.0428,622.8945,464.5887,20.8679,605.8041,489.5722,29.5002,65.8843,-256.7175,179.7802,54.745,-222.9974,179.533,-71.0346,145.2886,178.2371,-83.1928,178.3267,178.1311,64.2319,-171.1297,225.9256,-32.5492,110.6517,226.3979,249.3295,208.032,229.364,346.7756,-76.0747,229.8759 +7,0.06,731.865,81.2154,191.6668,650.5823,313.9242,178.9575,871.6771,201.6866,219.6054,837.72,307.6521,218.5385,700.5444,222.701,618.6846,710.9468,191.1897,615.7101,700.5444,222.701,618.6846,860.6551,259.7021,613.6773,873.3588,256.1401,479.9234,677.283,192.2499,429.6253,696.658,276.5514,638.1006,711.9498,340.5002,645.2896,692.9832,302.3214,618.9951,719.4495,313.7184,651.6699,703.5684,246.9075,639.1732,691.684,401.7141,622.2145,723.7615,414.3558,620.4497,761.6796,395.4103,629.6968,800.8299,371.5724,635.1343,687.5427,334.913,607.2216,836.1192,335.9472,625.2665,819.8867,343.6745,649.2634,852.6779,354.8873,502.7672,699.713,368.8935,639.1439,676.6093,425.084,469.4534,709.1238,447.6859,444.1038,692.4622,366.4985,371.4112,741.6734,435.6276,411.1588,740.3063,426.6806,354.441,710.947,445.8433,302.3418,714.1436,380.1906,283.9909,672.8358,470.3495,211.9695,631.1231,411.4533,204.7296,721.0777,429.1261,257.3415,613.3228,463.4481,159.1874,588.6562,484.5873,75.6324,612.0744,487.1593,98.5761,630.8891,463.375,51.5438,637.8078,469.8587,85.6421,596.9955,502.4135,-1.7176,615.7288,464.582,-11.4777,621.6799,466.4428,20.8978,605.5051,491.6311,30.0284,65.8847,-256.7162,179.7839,54.735,-222.9983,179.5404,-71.0262,145.2932,178.2334,-83.1828,178.3205,178.1278,64.2286,-171.1275,225.9271,-32.5427,110.6435,226.3782,249.3325,208.0359,229.3643,346.7778,-76.0976,229.8817 +8,0.07,731.5436,81.2676,191.6396,650.3708,313.996,178.9446,871.4277,201.6878,219.6162,837.4741,307.6818,218.5377,700.6545,222.6714,618.6876,711.0306,191.1586,615.7197,700.6545,222.6714,618.6876,860.7369,259.5137,613.5643,873.3242,255.9451,479.817,677.2006,192.1779,429.6592,696.8123,276.5482,638.0934,712.1973,340.487,645.2597,693.178,302.3289,618.9744,719.675,313.6966,651.6354,703.7161,246.8933,639.1695,692.0181,401.7376,622.1813,724.1459,414.2751,620.3403,761.9735,395.3269,629.6138,801.1002,371.4498,635.0197,687.7618,334.9442,607.2,836.3047,335.7752,625.1389,820.1428,343.5156,649.1398,852.7682,354.6583,502.6338,699.9965,368.9094,639.1183,676.8655,425.4579,469.5509,709.5641,447.8405,444.1577,691.2881,366.5144,371.3543,741.9234,435.5984,411.1216,740.3281,426.7813,354.3814,711.0136,446.2835,302.3956,712.3113,380.6388,283.8323,672.8341,471.3092,212.1889,630.4225,412.9484,205.0768,720.9061,429.6763,257.2918,613.0927,465.2618,159.6273,588.4196,487.2785,76.3259,612.1078,488.9261,99.0004,630.1285,464.8642,51.9117,637.2809,471.1248,85.9592,596.8442,504.7142,-0.9734,614.7637,466.5714,-10.8863,620.7213,468.238,21.291,605.2441,493.6523,30.6882,65.8732,-256.7181,179.7721,54.7548,-222.9955,179.5384,-71.0286,145.2899,178.2339,-83.1806,178.3168,178.1272,64.2229,-171.1284,225.9226,-32.5489,110.6325,226.3752,249.3352,208.0348,229.3661,346.7788,-76.1088,229.879 +9,0.08,731.2422,81.3203,191.6069,650.1561,314.0596,178.9322,871.1632,201.7021,219.6322,837.2224,307.7215,218.5477,700.7515,222.6577,618.694,711.1081,191.1311,615.7283,700.7515,222.6577,618.694,860.8255,259.335,613.4654,873.2861,255.7568,479.7062,677.1163,192.1156,429.6952,696.9707,276.5472,638.0722,712.4319,340.4868,645.2283,693.363,302.3442,618.9548,719.8997,313.6774,651.5976,703.8461,246.8861,639.1589,692.3254,401.7566,622.1597,724.463,414.2759,620.3184,762.2642,395.2461,629.5161,801.3705,371.327,634.913,687.9553,334.9753,607.179,836.4941,335.5933,624.9902,820.3796,343.3835,649.0234,852.8632,354.446,502.5022,700.2458,368.9172,639.0973,677.1235,425.8388,469.6312,709.9713,448.0085,444.2096,690.7983,366.7678,371.3461,742.1755,435.5681,411.1012,740.3582,426.8741,354.3608,711.1003,446.6908,302.4431,711.8683,381.0814,283.8032,672.8428,472.2363,212.4195,629.697,414.415,205.4236,720.6245,430.0512,257.2426,612.8696,466.9879,160.0739,588.2018,489.1032,76.8977,611.9949,490.7346,99.4799,629.3467,466.2931,52.3126,636.7767,472.4734,86.2834,596.6523,506.9524,-0.2063,612.5784,468.3626,-10.3772,619.8066,469.9642,21.7739,604.967,495.6135,31.3448,65.8741,-256.7164,179.7723,54.7394,-222.9959,179.5392,-71.0328,145.2881,178.2323,-83.1727,178.3143,178.1284,64.227,-171.1277,225.9276,-32.5528,110.6239,226.3573,249.3253,208.034,229.3697,346.7786,-76.1154,229.8812 +10,0.09,730.9497,81.3797,191.5814,649.9404,314.1363,178.9122,870.9164,201.7077,219.6495,836.9753,307.7556,218.5409,700.8419,222.6521,618.6994,711.1821,191.1059,615.7299,700.8419,222.6521,618.6994,860.9088,259.1758,613.3566,873.2527,255.5799,479.5984,677.0089,192.0613,429.7375,697.1223,276.5506,638.0631,712.651,340.4837,645.2033,693.5386,302.3583,618.9512,720.1004,313.6644,651.5659,703.9689,246.8845,639.1505,692.6288,401.7878,622.1623,724.787,414.2325,620.2568,762.5501,395.1761,629.4345,801.6043,371.2286,634.8015,688.1255,335.0073,607.1744,836.6772,335.4248,624.8586,820.6016,343.2511,648.917,852.9283,354.2541,502.3771,700.4952,368.927,639.0696,677.3534,426.2118,469.7366,710.3642,448.1689,444.2703,690.3633,367.3659,371.3731,742.4103,435.5315,411.0936,740.3693,426.9589,354.323,711.1564,447.1216,302.5056,711.4097,381.5131,283.7838,672.8264,473.1641,212.6488,629.0069,415.8318,205.7815,720.2393,430.2857,257.2336,612.6291,468.6551,160.5312,588.0092,491.251,77.5469,611.6638,492.5416,99.85,628.5712,467.6891,52.6933,636.0912,473.8569,86.4957,596.4934,509.1141,0.5567,611.417,470.2921,-9.8615,618.929,471.6269,22.2384,604.695,497.4669,31.9819,65.8775,-256.7207,179.7645,54.7426,-222.9972,179.5439,-71.0201,145.2947,178.2264,-83.1702,178.3225,178.1275,64.2273,-171.1302,225.9283,-32.5526,110.6186,226.3503,249.3284,208.0379,229.3703,346.7843,-76.0934,229.8179 +11,0.1,730.599,81.5755,191.4778,649.7112,314.1942,178.9058,870.6628,201.7237,219.6567,836.7402,307.7889,218.5394,700.9295,222.6387,618.707,711.2478,191.0897,615.7419,700.9295,222.6387,618.707,860.9718,259.0227,613.2372,873.2154,255.4137,479.4887,676.9099,192.0135,429.7704,697.2487,276.561,638.061,712.8612,340.4937,645.1843,693.6995,302.3839,618.9428,720.2944,313.6649,651.5406,704.0836,246.877,639.1505,692.9214,401.832,622.1504,725.096,414.2034,620.2014,762.8131,395.1086,629.3448,801.8352,371.1314,634.7001,688.2803,335.0517,607.1697,836.8438,335.2863,624.7281,820.8112,343.1317,648.7955,852.9939,354.0634,502.2507,700.7267,368.9588,639.0713,677.5969,426.5637,469.8969,710.7216,448.3259,444.3501,690.4075,368.0955,371.3913,742.6077,435.5104,411.0844,740.3705,427.0405,354.2864,711.195,447.5219,302.5639,710.887,381.9515,283.7594,672.8116,474.0446,212.8616,628.3251,417.232,206.1549,719.7335,430.5996,257.1392,612.3933,470.3066,161.0042,587.7812,493.3381,78.2019,611.7844,494.003,100.2589,627.7632,469.0348,53.0659,635.5703,475.1254,86.816,596.2957,511.1895,1.3132,610.321,472.1301,-9.3216,618.0888,473.2329,22.7336,604.4128,499.3213,32.6509,65.8694,-256.7162,179.7647,54.7399,-222.9928,179.5506,-71.0166,145.299,178.2204,-83.1685,178.3206,178.129,64.2261,-171.1313,225.9332,-32.5449,110.6194,226.3469,249.3277,208.0378,229.3714,346.7838,-76.0907,229.8206 +12,0.11,730.2902,81.7052,191.3901,649.4918,314.2527,178.8985,870.4014,201.7316,219.6371,836.4868,307.8172,218.537,701.0076,222.6547,618.7128,711.3132,191.0869,615.7493,701.0076,222.6547,618.7128,861.0336,258.9005,613.1363,873.1652,255.2578,479.3834,676.8127,191.9683,429.8089,697.3684,276.5909,638.0552,713.0609,340.4941,645.155,693.8442,302.4167,618.9503,720.4659,313.6715,651.5207,704.1783,246.8917,639.1448,693.1722,401.8744,622.1505,725.3789,414.1908,620.1506,763.0521,395.0659,629.2657,802.0547,371.0727,634.5943,688.4273,335.1044,607.1731,836.9762,335.1543,624.5759,821.0123,343.0398,648.6855,853.0468,353.879,502.13,700.9398,368.9937,639.0611,677.7593,426.9399,469.9492,711.0587,448.4999,444.4248,690.0025,368.5952,371.4205,742.7889,435.4855,411.0756,740.3503,427.1176,354.2615,711.1971,447.9188,302.6245,710.3258,382.372,283.7404,672.7499,474.8966,213.0839,627.6345,418.5752,206.5135,719.3968,430.9711,257.1013,612.1562,471.9163,161.4554,587.5673,495.3597,78.8354,611.4658,495.8383,100.7573,626.9677,470.4129,53.4859,635.0357,476.3484,87.1556,596.0936,513.2476,2.0579,609.2406,473.9701,-8.8024,617.27,474.8413,23.2355,604.1212,501.0906,33.2712,65.8723,-256.7175,179.7598,54.7508,-222.9928,179.5437,-71.0172,145.2967,178.2227,-83.152,178.3156,178.1286,64.22,-171.1328,225.9351,-32.5469,110.6082,226.3381,249.3327,208.0422,229.3705,346.7857,-76.0941,229.8214 +13,0.12,730.0178,81.7895,191.3201,649.2825,314.3067,178.8902,870.1828,201.7518,219.6427,836.249,307.8457,218.5245,701.0736,222.6674,618.7283,711.3799,191.086,615.7585,701.0736,222.6674,618.7283,861.0818,258.7872,613.0397,873.1078,255.1018,479.2856,676.6951,191.9314,429.8511,697.4808,276.631,638.0513,713.2394,340.5174,645.1389,693.9634,302.4496,618.9442,720.6218,313.6949,651.4991,704.2701,246.9111,639.1469,693.427,401.9197,622.1418,725.6626,414.1827,620.0981,763.2697,395.037,629.1824,802.2402,371.0087,634.4918,688.5638,335.1725,607.1735,836.9501,334.9948,624.3138,821.195,342.9715,648.5812,853.1012,353.7218,502.0075,701.1262,369.0394,639.0468,677.9472,427.2824,470.1036,711.3528,448.6752,444.4984,689.8625,369.2222,371.4511,742.9259,435.4829,411.0687,740.2994,427.2081,354.2411,711.2034,448.2854,302.6848,709.7654,382.7603,283.7375,672.6838,475.7265,213.2964,626.9574,419.8941,206.877,719.2891,431.3385,257.1428,611.9066,473.4742,161.9059,587.3375,497.3118,79.4496,611.3232,497.4315,101.2103,626.191,471.7131,53.8694,634.5233,477.5473,87.4746,595.913,515.2213,2.801,608.2247,475.7719,-8.2986,616.7964,476.301,23.967,603.785,502.8075,33.8769,65.8703,-256.7143,179.7581,54.7482,-222.9919,179.5613,-71.0167,145.2934,178.2214,-83.1469,178.3128,178.1278,64.2193,-171.134,225.943,-32.5417,110.6036,226.3295,249.3265,208.0372,229.3759,346.7852,-76.0928,229.8189 +14,0.13,729.7836,81.8378,191.2651,649.0582,314.3536,178.884,869.9564,201.7713,219.6386,836.0173,307.8777,218.5314,701.1285,222.6911,618.742,711.4309,191.0948,615.7664,701.1285,222.6911,618.742,861.1205,258.7087,612.9419,873.0638,254.976,479.1857,676.5939,191.9055,429.8918,697.5762,276.6698,638.0465,713.4062,340.5571,645.1171,694.0656,302.496,618.9461,720.7704,313.726,651.4688,704.3445,246.9418,639.1477,693.5962,401.9851,622.1852,725.8943,414.1867,620.0271,763.4671,395.0201,629.0803,802.4185,370.9666,634.3845,688.6685,335.2411,607.1669,837.0588,334.8862,624.1927,821.3561,342.9197,648.4739,853.1318,353.5869,501.8777,701.2951,369.0876,639.0433,678.0952,427.6303,470.1939,711.5927,448.8452,444.5574,688.4579,369.3581,371.4379,743.0345,435.4742,411.0497,740.2278,427.2745,354.1978,711.183,448.6273,302.7267,709.1757,383.1121,283.7122,672.6348,476.511,213.4897,626.2739,421.2115,207.2333,718.7186,431.589,257.0465,611.6469,474.9904,162.3419,587.0777,499.2012,80.049,611.1533,498.9793,101.647,625.4417,473.0325,54.2783,633.9703,478.7403,87.8053,595.7344,517.1389,3.5016,607.1592,477.54,-7.8041,615.6987,477.9509,24.2541,603.4816,504.5158,34.5167,65.8659,-256.7095,179.7571,54.7466,-222.9918,179.5633,-71.0152,145.2944,178.2197,-83.149,178.3224,178.1292,64.2242,-171.1358,225.9432,-32.5423,110.5989,226.3247,249.3324,208.0393,229.3726,346.7892,-76.096,229.8197 +15,0.14,729.5568,81.8702,191.2217,648.8175,314.3896,178.869,869.7159,201.8064,219.6207,835.784,307.8915,218.5076,701.1799,222.7171,618.7527,711.4882,191.1151,615.7713,701.1799,222.7171,618.7527,861.1646,258.6452,612.8472,873.0172,254.8572,479.0989,676.4867,191.8826,429.9218,697.6503,276.7152,638.0421,713.5159,340.5938,645.0942,694.1546,302.5448,618.9354,720.8941,313.7584,651.4333,704.4016,246.9722,639.1471,693.8468,402.0382,622.1029,726.1058,414.1922,619.9485,763.6436,395.0252,628.989,802.576,370.9478,634.2802,688.7524,335.31,607.1593,837.158,334.8177,624.0723,821.5755,342.9347,648.4476,853.1383,353.4676,501.753,701.4508,369.1495,639.0217,678.1742,427.9658,470.2483,711.8098,449.0248,444.6144,688.0596,369.8045,371.4468,743.0936,435.4841,411.0146,740.108,427.3308,354.1508,711.1255,448.962,302.7587,708.4832,383.4923,283.6983,672.5393,477.2694,213.6817,625.5834,422.4717,207.5819,718.3477,431.9232,257.0309,611.465,476.4154,162.7021,586.8411,501.0357,80.6315,611.1241,500.2903,102.0441,624.7525,474.3156,54.6481,633.446,479.8964,88.1224,595.5757,518.98,4.2182,606.2379,479.2349,-7.326,615.0295,479.4172,24.8024,603.1487,506.1576,35.0935,65.8675,-256.7128,179.7588,54.7456,-222.9909,179.5599,-71.014,145.3009,178.2198,-83.1468,178.3237,178.1273,64.2254,-171.1344,225.9343,-32.543,110.5982,226.321,249.3345,208.0406,229.3706,346.786,-76.0927,229.8203 +16,0.15,729.3408,81.8773,191.174,648.5724,314.4111,178.8394,869.4542,201.8264,219.6141,835.5311,307.9041,218.4937,701.2132,222.753,618.7741,711.5287,191.1329,615.782,701.2132,222.753,618.7741,861.1846,258.6088,612.7542,872.9565,254.7565,479.0129,676.3694,191.8602,429.9522,697.711,276.7648,638.0388,713.6309,340.6469,645.0683,694.2161,302.6092,618.9359,720.9888,313.8047,651.4111,704.4504,247.0175,639.158,693.9908,402.0963,622.0815,726.2685,414.2045,619.8752,763.7822,395.0485,628.8997,802.7019,370.9536,634.1687,688.8103,335.3806,607.1573,837.2441,334.7756,623.9577,821.6508,342.8818,648.2631,853.1249,353.3745,501.6287,701.5667,369.2076,639.0004,678.2654,428.2777,470.3538,711.9756,449.1974,444.6528,687.6201,370.2214,371.4654,743.1141,435.4881,410.9782,739.9719,427.3831,354.1161,711.0391,449.3156,302.798,707.8092,383.8723,283.6756,672.3833,478.0374,213.8707,624.8836,423.6924,207.8997,717.928,432.2525,256.9853,611.1102,477.8824,163.1543,586.5475,503.0434,81.233,610.7527,501.9203,102.4546,623.7814,475.5661,54.9537,632.913,481.0164,88.4206,595.4427,520.7762,4.8641,605.3371,480.8786,-6.8793,613.6203,480.6509,25.267,602.837,507.6945,35.6338,65.8628,-256.7099,179.7542,54.758,-222.9923,179.544,-71.0184,145.2967,178.2216,-83.1472,178.324,178.1281,64.2265,-171.136,225.9343,-32.5462,110.5928,226.3194,249.3299,208.0412,229.3695,346.7856,-76.087,229.8198 +17,0.16,729.1331,81.8646,191.1232,648.3206,314.4113,178.7998,869.1896,201.8465,219.6086,835.2534,307.9112,218.4949,701.2516,222.7984,618.7972,711.5712,191.1787,615.7987,701.2516,222.7984,618.7972,861.1855,258.6064,612.6469,872.8735,254.6808,478.9173,676.2564,191.8497,429.9855,697.7493,276.8195,638.0391,713.715,340.6902,645.0369,694.2587,302.668,618.9402,721.0529,313.8703,651.3939,704.4929,247.0666,639.1676,694.1153,402.1565,622.0444,726.3947,414.2274,619.7803,763.8752,395.0786,628.7979,802.8041,370.9911,634.0657,688.8472,335.4605,607.1519,837.2998,334.7654,623.8238,821.7951,342.9557,648.2316,853.0984,353.3077,501.4996,701.6484,369.2796,638.9763,678.2944,428.6088,470.4203,712.1046,449.3677,444.6793,687.1268,370.6496,371.4699,743.1115,435.485,410.927,739.8079,427.4465,354.0577,710.8936,449.6593,302.8382,707.1266,384.2487,283.64,672.2263,478.7826,214.0513,624.1999,424.8815,208.2008,717.5145,432.5817,256.9538,610.9073,479.189,163.4512,586.3066,504.5027,81.6833,610.7341,503.1329,102.814,623.1422,476.7709,55.3031,632.3592,482.1027,88.6824,595.2084,522.4471,5.4491,604.4703,482.4985,-6.4928,613.5073,482.222,25.7494,602.5159,509.2343,36.1386,65.8645,-256.7089,179.7557,54.7437,-222.9925,179.5517,-71.0141,145.2999,178.2208,-83.145,178.3284,178.1268,64.2202,-171.1329,225.9369,-32.5371,110.5961,226.3181,249.3302,208.0415,229.3722,346.7792,-76.2143,229.9137 +18,0.17,728.933,81.8356,191.0645,648.0629,314.3689,178.7289,868.9499,201.8562,219.6308,834.959,307.9178,218.4954,701.2797,222.8475,618.8148,711.6199,191.2122,615.8133,701.2797,222.8475,618.8148,861.1823,258.642,612.5506,872.7765,254.6351,478.8041,676.1218,191.84,430.0114,697.7783,276.8833,638.0463,713.754,340.7677,645.0076,694.2708,302.7318,618.9492,721.0958,313.9374,651.3674,704.5321,247.128,639.1765,694.1835,402.2161,622.0063,726.463,414.2623,619.6906,763.9316,395.1302,628.6908,802.8626,371.0572,633.9443,688.8419,335.5312,607.1495,837.3264,334.7952,623.6909,821.8428,343.0295,648.1237,853.035,353.2787,501.3672,701.6949,369.346,638.9538,678.2754,428.9158,470.4726,712.2074,449.5335,444.7066,686.9133,371.2847,371.4931,743.0544,435.491,410.871,739.5987,427.5233,354.0005,710.7249,449.9974,302.8497,706.4341,384.5683,283.6186,672.0477,479.4822,214.2021,623.5275,426.0317,208.4835,717.1101,432.9173,256.9269,610.613,480.5068,163.7977,586.064,506.1341,82.1548,610.5032,504.4673,103.1497,622.4673,477.9486,55.6407,631.8395,483.1891,88.9519,595.0932,524.1147,6.0224,603.6338,484.0739,-6.0978,612.0391,483.4091,26.0054,602.2555,510.7235,36.6478,65.8624,-256.7084,179.7589,54.7512,-222.9915,179.5473,-71.0185,145.3062,178.2241,-83.1495,178.3209,178.1306,64.2262,-171.1348,225.9319,-32.5479,110.5955,226.3302,249.3264,208.0434,229.375,346.778,-76.225,229.9123 +19,0.18,728.7415,81.7944,190.9815,647.8064,314.3283,178.6391,868.6562,201.862,219.6383,834.6655,307.9194,218.5118,701.3109,222.8987,618.8395,711.6738,191.2612,615.83,701.3109,222.8987,618.8395,861.1804,258.7125,612.4359,872.6397,254.6432,478.6896,675.9839,191.823,430.0327,697.795,276.9451,638.0411,713.7494,340.8531,644.9713,694.2554,302.7978,618.941,721.1122,314.021,651.3417,704.5532,247.1907,639.1896,694.2031,402.2856,621.9551,726.4922,414.285,619.5126,763.9301,395.2067,628.5581,802.8681,371.1505,633.8282,688.8038,335.6136,607.1466,837.3227,334.8637,623.5486,821.8158,343.0922,647.9181,852.9318,353.2851,501.2228,701.6981,369.4258,638.913,678.2203,429.214,470.5302,712.2571,449.6773,444.7307,686.311,371.5912,371.4852,742.9719,435.4921,410.8122,739.361,427.6281,353.9346,710.5285,450.3375,302.867,705.6978,384.9297,283.5724,671.8514,480.1727,214.3468,622.8756,427.137,208.7283,716.6961,433.2267,256.8839,610.2987,481.7843,164.1248,585.8441,507.9654,82.5732,610.2725,505.7668,103.4734,622.0904,479.1859,56.0011,631.3082,484.2301,89.1698,595.0063,525.7386,6.5945,602.8189,485.5642,-5.7584,611.7,484.6227,26.3452,601.9751,512.1324,37.0988,65.8675,-256.7098,179.7525,54.7388,-222.9915,179.5565,-71.0247,145.3032,178.2245,-83.149,178.3216,178.1311,64.2246,-171.1342,225.9396,-32.5411,110.6051,226.3191,249.3295,208.0446,229.3782,346.7835,-76.2436,229.9077 +20,0.19,728.5304,81.7411,190.921,647.5211,314.2614,178.5365,868.3958,201.8638,219.6848,834.3603,307.9113,218.5418,701.3234,222.9569,618.8593,711.7338,191.3164,615.8386,701.3234,222.9569,618.8593,861.174,258.8372,612.312,872.5157,254.6808,478.5577,675.834,191.8211,430.0697,697.7856,277.0332,638.0382,713.6983,340.9393,644.9244,694.215,302.8652,618.9423,721.113,314.1155,651.2939,704.566,247.2672,639.1895,694.1732,402.3527,621.8837,726.4638,414.3485,619.4059,763.8863,395.3087,628.4334,802.8494,371.2875,633.6832,688.7385,335.6937,607.1207,837.2809,334.9921,623.3976,821.8172,343.2328,647.7922,852.8155,353.3321,501.0727,701.6605,369.513,638.8659,678.1315,429.4996,470.5627,712.2443,449.8404,444.7302,685.8105,371.9951,371.4767,742.844,435.5293,410.7456,739.0807,427.7313,353.8765,710.3276,450.6561,302.8697,704.9615,385.381,283.5254,671.6209,480.8539,214.4852,622.2328,428.2071,208.9612,716.2368,433.5635,256.8362,609.9894,482.9949,164.4224,585.5319,509.419,83.0293,610.0499,507.0266,103.7808,621.267,480.2223,56.2397,630.7979,485.2465,89.4128,595.0408,527.355,7.1649,602.0292,487.0463,-5.45,611.0583,485.9043,26.6847,601.7048,513.5054,37.5377,65.866,-256.7103,179.757,54.7377,-222.9935,179.5557,-71.0184,145.2967,178.2216,-83.1524,178.3183,178.1305,64.2224,-171.134,225.9341,-32.5503,110.5955,226.3203,249.3286,208.0441,229.3713,346.7849,-76.2613,229.8984 +21,0.2,728.3266,81.6936,190.8673,647.2324,314.1887,178.4441,868.106,201.8555,219.7418,834.0439,307.9098,218.5858,701.3317,223.0264,618.893,711.7983,191.3981,615.8602,701.3317,223.0264,618.893,861.1424,258.9986,612.1843,872.3612,254.7608,478.4296,675.6656,191.7927,430.101,697.7725,277.1179,638.0298,713.5952,341.0536,644.8858,694.1609,302.945,618.9373,721.0793,314.2199,651.2513,704.5854,247.3557,639.1981,694.093,402.4284,621.8201,726.3817,414.4257,619.2936,763.8049,395.426,628.295,802.7892,371.4376,633.5477,688.6401,335.7885,607.1026,837.2218,335.1521,623.2571,821.7824,343.4154,647.6493,852.6409,353.4207,500.899,701.5768,369.6117,638.8163,677.9729,429.7793,470.58,712.1867,450.0083,444.7375,685.3277,372.4103,371.443,742.6713,435.568,410.6939,738.7911,427.8243,353.7984,710.0922,450.998,302.8778,704.2515,385.7551,283.4731,671.3893,481.5099,214.6106,621.5996,429.2426,209.1573,715.7527,433.9003,256.7868,609.6774,484.1696,164.6693,585.3369,510.7425,83.3311,609.8124,508.2314,104.0269,621.0046,481.2047,56.4932,630.3041,486.2255,89.5953,594.8939,528.8538,7.6331,601.3152,488.5111,-5.1665,609.9649,487.2882,26.8923,601.4933,514.8074,37.9435,65.8606,-256.7122,179.7626,54.7453,-222.9921,179.5521,-71.0201,145.2979,178.2202,-83.1421,178.3178,178.1239,64.2225,-171.1333,225.9341,-32.5507,110.5943,226.3251,249.3319,208.0425,229.373,346.7876,-76.0915,229.8213 +22,0.21,728.1031,81.5855,190.8201,646.944,314.0934,178.3699,867.8122,201.832,219.8099,833.704,307.8986,218.6271,701.3422,223.1136,618.9224,711.8556,191.4785,615.8841,701.3422,223.1136,618.9224,861.1054,259.1994,612.0317,872.1984,254.8684,478.2793,675.4852,191.7656,430.1584,697.7305,277.214,638.0211,713.5003,341.1704,644.8329,694.0846,303.0279,618.9314,721.0203,314.349,651.2137,704.5844,247.4559,639.2094,693.9763,402.5197,621.7374,726.2366,414.53,619.1645,763.6821,395.5718,628.1576,802.6789,371.6352,633.4019,688.5251,335.891,607.0707,837.1432,335.3506,623.0938,821.7238,343.6371,647.5101,852.419,353.542,500.7219,701.4653,369.7254,638.7542,677.7712,430.0568,470.5706,712.0903,450.1825,444.7224,684.6492,372.7032,371.4054,742.4492,435.6323,410.6205,738.4812,427.9501,353.7161,709.8108,451.3327,302.8625,703.4926,386.1632,283.4027,671.1301,482.1577,214.6885,620.9637,430.2315,209.3249,715.3142,434.24,256.7437,609.3691,485.282,164.896,585.0937,512.3121,83.7043,609.399,509.5723,104.2978,620.4106,482.2717,56.6945,629.8213,487.1778,89.7408,594.8839,530.3136,8.1085,600.6235,489.9058,-4.9454,609.6695,488.4005,27.1378,601.2647,516.0932,38.2935,65.8684,-256.7115,179.7477,54.7388,-222.9911,179.5563,-71.0265,145.3044,178.223,-83.1484,178.3213,178.1313,64.2244,-171.1326,225.9364,-32.548,110.5946,226.3208,249.3318,208.0422,229.368,346.7831,-76.0941,229.819 +23,0.22,727.8829,81.4673,190.8045,646.6509,313.9844,178.2711,867.5157,201.7968,219.8722,833.3794,307.8828,218.6864,701.3464,223.1978,618.9543,711.9213,191.5675,615.9002,701.3464,223.1978,618.9543,861.0521,259.4438,611.894,872.0107,254.9986,478.1286,675.2982,191.7159,430.215,697.6854,277.3219,638.02,713.3765,341.3016,644.7821,693.9797,303.1254,618.9253,720.9487,314.4871,651.1708,704.564,247.5693,639.2183,693.8125,402.6197,621.6502,726.0694,414.6396,619.0333,763.5046,395.7446,628.0096,802.54,371.8602,633.2479,688.3672,335.9885,607.0378,837.0356,335.5905,622.9248,821.621,343.8837,647.3565,852.1639,353.7091,500.5337,701.3125,369.8477,638.7037,677.5356,430.3183,470.5658,711.9109,450.3754,444.6984,684.2794,373.2158,371.3602,742.221,435.7108,410.5449,738.1261,428.1147,353.6286,709.4957,451.6853,302.8524,702.7993,386.5671,283.3255,670.8433,482.8178,214.7847,620.3483,431.1981,209.4832,714.7925,434.6068,256.7003,609.058,486.3817,165.0715,584.8865,513.4469,83.9484,609.3337,510.5156,104.474,619.656,483.4129,56.891,629.381,488.0949,89.8814,594.8565,531.743,8.5137,599.9905,491.2354,-4.7257,609.0828,489.5887,27.3,601.0827,517.3469,38.6259,65.8638,-256.7102,179.7555,54.7387,-222.9916,179.5555,-71.0183,145.3077,178.2218,-83.151,178.3229,178.1296,64.2243,-171.1286,225.9451,-32.5478,110.5945,226.3134,249.3251,208.0416,229.3729,346.781,-76.0899,229.8227 +24,0.23,727.6606,81.2249,190.8572,646.3393,313.8748,178.1886,867.2216,201.7547,219.9487,832.8006,307.7671,218.6235,701.3507,223.2911,618.9716,711.9839,191.6682,615.9151,701.3507,223.2911,618.9716,860.995,259.7034,611.7498,871.792,255.1649,477.969,675.1033,191.6545,430.2825,697.6301,277.4237,638.0066,713.2124,341.4384,644.7277,693.86,303.2206,618.9128,720.8633,314.64,651.1057,704.5563,247.6725,639.2191,693.6247,402.7234,621.5638,725.843,414.7894,618.9417,763.2947,395.9284,627.842,802.3829,372.0916,633.085,688.1965,336.0991,606.9962,836.899,335.8712,622.7629,821.509,344.1712,647.1981,851.869,353.8879,500.3394,701.1528,369.9754,638.6357,677.2614,430.58,470.5608,711.6942,450.5717,444.6692,683.5734,373.4848,371.2859,741.9239,435.8378,410.4727,737.7379,428.2823,353.5321,709.1059,452.0427,302.8076,702.0831,386.9803,283.2583,670.5264,483.4539,214.8864,619.7441,432.1239,209.5804,714.2609,434.9851,256.6642,608.7245,487.4302,165.2409,584.6655,514.7277,84.1875,609.1194,511.589,104.6269,619.1702,484.4407,57.0416,628.9244,489.0253,90.0022,594.8831,533.1259,8.9056,599.41,492.5775,-4.5689,608.2426,490.8478,27.4324,600.8963,518.5579,38.9035,65.8634,-256.7148,179.756,54.7398,-222.9927,179.5574,-71.0215,145.3009,178.2226,-83.149,178.3216,178.1311,64.2192,-171.1338,225.9438,-32.5411,110.5946,226.3146,249.3216,208.0378,229.3701,346.7843,-76.0918,229.8206 +25,0.24,727.4245,81.0269,190.8754,646.0209,313.765,178.0931,866.8998,201.6933,220.0158,832.4553,307.7199,218.669,701.3542,223.3777,618.9762,712.0544,191.7702,615.9243,701.3542,223.3777,618.9762,860.9329,259.9987,611.5933,871.6,255.3647,477.8123,674.9138,191.5957,430.3453,697.5608,277.5107,638.0031,713.0141,341.5942,644.6718,693.7255,303.3301,618.895,720.7413,314.8047,651.0637,704.5239,247.7913,639.2073,693.3995,402.831,621.4731,725.6138,414.9149,618.8057,763.0762,396.1302,627.6844,802.1873,372.3428,632.9059,688.0033,336.2008,606.9579,836.7289,336.1552,622.5795,821.3773,344.4662,647.0375,851.5737,354.0854,500.144,700.9462,370.0996,638.5699,676.9529,430.8529,470.565,711.4347,450.7847,444.6432,683.1694,373.9784,371.2368,741.584,435.9794,410.3928,737.3035,428.4897,353.4644,708.7078,452.4084,302.8066,701.3622,387.3654,283.1963,670.2088,484.0688,214.9651,619.1437,433.0366,209.6845,713.7296,435.3164,256.6508,608.4003,488.4547,165.3881,584.4437,516.1187,84.464,608.893,512.6583,104.7826,617.8151,485.3566,57.1307,628.5187,489.9427,90.111,594.8906,534.454,9.2529,598.9008,493.8834,-4.4177,608.0354,491.9019,27.5585,600.7596,519.7475,39.1989,65.8678,-256.715,179.7603,54.7484,-222.9927,179.555,-71.0084,145.2977,178.2192,-83.1476,178.3217,178.1299,64.2225,-171.1351,225.9386,-32.5432,110.5949,226.3179,249.3284,208.0376,229.3728,346.786,-76.1021,229.8205 +26,0.25,727.1745,80.9149,190.8475,645.6964,313.6384,177.9889,866.5881,201.6407,220.0724,832.1047,307.6683,218.705,701.351,223.4745,618.9775,712.1199,191.8814,615.9132,701.351,223.4745,618.9775,860.8555,260.3164,611.4402,871.3818,255.5955,477.6445,674.7151,191.5431,430.4091,697.4733,277.4959,638.0343,712.8174,341.7436,644.6061,693.576,303.4312,618.8742,720.6165,314.9663,651.0029,704.4824,247.9039,639.1863,693.1639,402.9356,621.3798,725.3699,415.0592,618.6823,762.8189,396.3335,627.5184,801.9906,372.611,632.7302,687.7982,336.3197,606.9122,836.5677,336.4752,622.3952,821.2087,344.7753,646.8651,851.2369,354.3195,499.9471,700.7303,370.2335,638.4937,676.6249,431.1048,470.5732,711.1422,450.9976,444.6227,682.6101,374.3722,371.1813,741.1931,436.1584,410.3222,736.8483,428.7192,353.3848,708.2977,452.7422,302.8011,700.5571,387.7654,283.1464,669.8785,484.6729,215.0326,618.5343,433.9114,209.7581,713.2136,435.6948,256.6338,608.0462,489.4323,165.5146,584.2341,517.3005,84.6687,608.737,513.6768,104.9785,618.2101,486.4469,57.3186,628.0464,490.8751,90.162,594.8718,535.7628,9.5727,598.4543,495.1933,-4.3115,607.3574,493.1358,27.7111,600.6171,520.9083,39.4397,65.8646,-256.7104,179.7456,54.7542,-222.9933,179.5527,-71.012,145.3,178.2205,-83.1405,178.3125,178.1264,64.2279,-171.1321,225.9386,-32.54,110.5901,226.3171,249.33,208.0385,229.3712,346.786,-76.102,229.8203 +27,0.26,726.9355,80.8163,190.8126,645.377,313.5144,177.8831,866.2922,201.5949,220.116,831.7609,307.6202,218.73,701.3184,223.5659,618.9633,712.1835,191.9855,615.8974,701.3184,223.5659,618.9633,860.7609,260.6474,611.2823,871.147,255.8439,477.4709,674.5201,191.4899,430.4659,697.3762,277.5659,638.0189,712.6062,341.8952,644.5402,693.4293,303.5333,618.8389,720.4681,315.1304,650.9283,704.4273,248.015,639.1523,692.9092,403.0519,621.287,725.0972,415.183,618.5117,762.5558,396.5527,627.3499,801.7639,372.8839,632.551,687.5796,336.424,606.8616,836.3848,336.7972,622.1911,821.0856,345.1371,646.7643,850.8636,354.567,499.7506,700.4872,370.3748,638.42,676.2305,431.3965,470.5499,710.7906,451.2513,444.6187,681.8248,374.6123,371.1236,740.7712,436.3483,410.2416,736.354,428.952,353.3145,707.8351,453.1011,302.8058,699.7583,388.1577,283.0996,669.5108,485.2581,215.1226,617.9366,434.7805,209.8266,712.6023,436.037,256.6554,607.7156,490.3847,165.6296,584.0484,518.2814,84.8039,608.4914,514.6803,105.1122,617.5543,487.2773,57.4169,627.6691,491.79,90.2383,594.8456,537.0522,9.8625,598.0932,496.4619,-4.1838,607.1137,494.1878,27.7044,600.4719,522.0634,39.664,65.8586,-256.713,179.7513,54.7561,-222.9915,179.5516,-71.0184,145.2967,178.2216,-83.1447,178.3162,178.1274,64.2251,-171.1324,225.9309,-32.5362,110.5985,226.3141,249.33,208.0439,229.3701,346.7874,-76.0939,229.8208 +28,0.27,726.7012,80.7258,190.7871,645.0541,313.3867,177.7799,865.9847,201.5266,220.1673,831.4085,307.5689,218.7457,701.2849,223.661,618.9593,712.2369,192.0956,615.8848,701.2849,223.661,618.9593,860.6712,261.0013,611.1344,870.9293,256.107,477.3151,674.3373,191.4098,430.4891,697.2452,277.67,637.9807,712.3569,342.0541,644.4777,693.2464,303.6328,618.8063,720.3073,315.2892,650.8524,704.3544,248.1311,639.1119,692.6283,403.1658,621.1991,724.8148,415.3285,618.3818,762.2778,396.7709,627.1696,801.5166,373.161,632.3574,687.3514,336.5313,606.8124,836.3101,337.2249,622.0341,820.8264,345.4321,646.504,850.467,354.8275,499.5486,700.2216,370.5166,638.3496,675.8834,431.6696,470.6091,710.4341,451.4886,444.6121,681.3077,375.1195,371.1024,740.3377,436.5487,410.1651,735.7975,429.1765,353.2406,707.3193,453.44,302.8214,698.9727,388.548,283.0667,669.1218,485.8712,215.2416,617.3144,435.6181,209.8842,711.6548,436.3276,256.7172,607.3484,491.3062,165.7388,583.8083,519.3925,84.9712,608.2537,515.6424,105.2283,617.1658,488.303,57.4973,627.3141,492.6911,90.3359,594.8118,538.3173,10.1599,597.7253,497.7567,-4.0874,606.6105,495.407,27.8852,600.3033,523.1898,39.8754,65.8651,-256.7144,179.7563,54.7407,-222.993,179.5504,-71.0247,145.2933,178.2228,-83.1477,178.3121,178.1279,64.224,-171.1352,225.936,-32.5392,110.5949,226.3176,249.3237,208.0424,229.375,346.789,-76.1919,229.8342 +29,0.28,726.4591,80.6161,190.7894,644.7297,313.2716,177.6675,865.6729,201.4619,220.1909,831.0687,307.5263,218.7512,701.2534,223.7359,618.9374,712.2812,192.1876,615.872,701.2534,223.7359,618.9374,860.5634,261.3741,610.9918,870.6935,256.3893,477.1563,674.1611,191.332,430.502,697.1177,277.7992,637.9208,712.108,342.2227,644.4159,693.0627,303.7315,618.7546,720.1302,315.4672,650.7787,704.2888,248.237,639.0589,692.3393,403.2982,621.1204,724.4956,415.4971,618.2584,761.9863,397.0143,626.9933,801.2498,373.4749,632.1688,687.094,336.6535,606.7601,835.9774,337.479,621.7951,820.6139,345.7754,646.3273,850.0731,355.1004,499.334,699.9633,370.6544,638.2676,675.4954,431.9616,470.603,710.0571,451.7302,444.6005,680.405,375.3009,371.0637,739.8683,436.7476,410.0906,735.2358,429.3885,353.1662,706.782,453.8004,302.8628,698.1983,388.9619,283.0471,668.6876,486.4608,215.3404,616.6718,436.434,209.9366,711.2722,436.7987,256.7384,606.9698,492.1911,165.8104,583.583,520.4777,85.1101,607.9816,516.6191,105.3527,616.8363,489.3216,57.5816,626.9682,493.6178,90.4283,594.7449,539.5898,10.4227,597.4509,499.0346,-4.003,606.4807,496.4571,27.8578,600.1428,524.3185,40.0759,65.8588,-256.7176,179.7471,54.7454,-222.9925,179.5524,-71.0135,145.2991,178.2188,-83.1548,178.3195,178.1291,64.2207,-171.1301,225.9391,-32.5482,110.5911,226.3196,249.325,208.0393,229.3746,346.7808,-76.0975,229.8213 +30,0.29,726.2325,80.4986,190.7955,644.4233,313.1568,177.5621,865.3957,201.3936,220.2316,830.7404,307.4704,218.7399,701.2366,223.8398,618.8966,712.3287,192.2906,615.8439,701.2366,223.8398,618.8966,860.4626,261.7532,610.8399,870.4638,256.6609,477.0012,674.0212,191.25,430.5222,696.988,277.9323,637.8528,711.8705,342.3832,644.3312,692.8725,303.8354,618.7025,719.9447,315.6385,650.7076,704.1997,248.3423,639.0042,692.061,403.4152,621.0317,724.1977,415.6488,618.1287,761.6769,397.2449,626.8268,800.9977,373.7571,631.9852,686.8402,336.7684,606.7081,835.7706,337.8264,621.611,820.4032,346.13,646.1416,849.6685,355.3897,499.1416,699.6962,370.7914,638.1922,675.1212,432.2404,470.6602,709.6815,451.9683,444.5878,679.8568,375.8375,371.0581,739.3811,436.9466,410.0049,734.6495,429.655,353.1064,706.1764,454.1753,302.8817,697.3575,389.3689,283.0251,668.2136,487.0626,215.4392,616.0323,437.2297,209.9507,710.6071,437.1736,256.7735,606.5499,493.055,165.8891,583.3291,521.5448,85.2138,607.6925,517.5716,105.4507,616.4606,490.3163,57.6179,626.6187,494.5156,90.4996,594.7007,540.8022,10.6404,597.2095,500.3146,-3.9497,606.0214,497.6732,28.0581,599.9673,525.4392,40.259,65.8627,-256.7171,179.7534,54.7389,-222.9913,179.5573,-71.0085,145.3015,178.216,-83.1529,178.3186,178.1313,64.2246,-171.1322,225.9338,-32.5437,110.5914,226.3203,249.3242,208.0398,229.3746,346.7877,-76.0945,229.8202 +31,0.3,726.012,80.366,190.8043,644.1221,313.0418,177.4744,865.1077,201.3159,220.2318,830.426,307.4103,218.7263,701.2058,223.933,618.858,712.3651,192.3944,615.8176,701.2058,223.933,618.858,860.3782,262.1458,610.7232,870.252,256.9479,476.8541,673.8884,191.1675,430.5339,696.8396,278.0799,637.7735,711.6225,342.5421,644.2512,692.6833,303.9423,618.6469,719.741,315.8125,650.6328,704.1191,248.4491,638.9411,691.7641,403.5288,620.9273,723.8567,415.8199,618.0165,761.3493,397.4831,626.6486,800.7224,374.0636,631.8022,686.5672,336.882,606.6476,835.5322,338.1894,621.4324,820.1772,346.4766,645.9732,849.2846,355.6807,498.9495,699.4131,370.9382,638.1061,674.6857,432.532,470.6869,709.286,452.1805,444.5594,679.1014,376.2078,371.0056,738.8934,437.1376,409.9193,734.0095,429.8934,353.0281,705.539,454.5533,302.9047,696.3185,389.7349,282.9148,667.7128,487.6618,215.5383,615.3857,437.997,209.9423,709.9044,437.584,256.8201,606.1154,493.9168,165.9352,583.0718,522.713,85.3452,607.3997,518.5344,105.5446,616.1144,491.3116,57.6648,626.2678,495.4218,90.5562,594.6325,542.0155,10.8443,596.9478,501.5318,-3.906,605.7213,498.7866,28.0911,599.8362,526.4998,40.4042,65.8639,-256.713,179.75,54.7485,-222.9926,179.556,-71.0116,145.3,178.2177,-83.1535,178.3189,178.1311,64.2193,-171.1318,225.9382,-32.5447,110.5925,226.3198,249.3314,208.0403,229.3708,346.7872,-76.1031,229.8217 +32,0.31,725.8188,80.272,190.7973,643.8247,312.9358,177.3868,864.8535,201.2476,220.248,830.1198,307.3565,218.7174,701.1336,224.0201,618.8374,712.3712,192.4889,615.8111,701.1336,224.0201,618.8374,860.2713,262.5392,610.6118,870.0258,257.2556,476.7281,673.7651,191.0852,430.5566,696.6716,278.2236,637.7044,711.3734,342.7087,644.1556,692.463,304.0344,618.5872,719.5372,315.9842,650.5526,703.8547,248.4963,638.9083,691.4335,403.644,620.8184,723.5314,415.9465,617.8265,761.0228,397.7162,626.4745,800.4324,374.3759,631.635,686.2873,336.9928,606.5779,835.377,338.6097,621.2899,819.9372,346.8345,645.8192,848.8974,355.9691,498.7743,699.1172,371.0826,637.9973,674.2116,432.8042,470.656,708.8643,452.4114,444.538,678.3157,376.6147,370.939,738.3699,437.3034,409.836,733.3852,430.1512,352.9558,704.8843,454.9461,302.9361,695.6002,390.2533,282.9407,667.1796,488.2806,215.6458,614.7111,438.7784,209.919,709.3531,437.9534,256.8216,605.6616,494.7817,165.979,582.8197,523.7477,85.4081,607.1049,519.4697,105.6145,616.2491,492.4421,57.7784,625.8923,496.3183,90.5823,594.5721,543.1879,11.0261,596.7114,502.7266,-3.8786,605.7963,499.9623,28.2052,599.6863,527.6248,40.5498,65.8627,-256.7114,179.761,54.7408,-222.9924,179.55,-71.0122,145.2997,178.2213,-83.1456,178.3186,178.129,64.2191,-171.1334,225.9319,-32.545,110.5897,226.3207,249.3326,208.0392,229.3718,346.7845,-76.1024,229.8221 +33,0.32,725.6296,80.1822,190.8091,643.5405,312.8285,177.3176,864.6111,201.183,220.2522,829.8528,307.2796,218.7013,701.0823,224.1038,618.8031,712.377,192.5859,615.7919,701.0823,224.1038,618.8031,860.1485,262.9268,610.5114,869.8321,257.5338,476.6107,673.6558,191.0116,430.5743,696.4875,278.3707,637.6294,711.0911,342.8558,644.0693,692.2394,304.1247,618.5352,719.3227,316.1522,650.4783,703.7336,248.5993,638.8456,691.086,403.7427,620.6966,723.168,416.0901,617.6713,760.6766,397.9386,626.3023,800.1093,374.6808,631.4645,686.0118,337.0939,606.499,835.03,338.9085,621.1052,819.6773,347.1946,645.6765,848.5187,356.2725,498.6305,698.8033,371.193,637.8897,673.7407,433.0589,470.6613,708.4155,452.6246,444.507,677.4257,376.8009,370.9128,737.825,437.4889,409.7327,732.6945,430.4072,352.8675,704.1912,455.3458,302.9664,694.5833,390.6155,282.82,666.6285,488.8901,215.733,614.0132,439.5486,209.8961,708.4216,438.4539,256.9148,605.1825,495.6328,166.0142,582.5305,524.6733,85.4435,606.7876,520.407,105.6677,615.4088,493.2816,57.7155,625.4897,497.211,90.6219,594.4985,544.35,11.1756,596.4247,503.8805,-3.8522,605.1121,500.9197,28.1556,599.4714,528.6583,40.6471,65.8678,-256.7115,179.7535,54.7495,-222.9924,179.5467,-71.0195,145.2982,178.22,-83.1481,178.321,178.1294,64.2205,-171.1352,225.9382,-32.5376,110.5972,226.3203,249.3263,208.0378,229.3748,346.7851,-76.0961,229.8281 +34,0.33,725.4586,80.104,190.8096,643.2735,312.7288,177.2542,864.3863,201.1176,220.2377,829.5917,307.2101,218.6807,701.043,224.1944,618.7653,712.3792,192.6788,615.7786,701.043,224.1944,618.7653,860.0287,263.318,610.4238,869.6424,257.8232,476.4964,673.5605,190.9267,430.598,696.3036,278.4877,637.5649,710.8105,342.9902,643.9736,692.0073,304.2039,618.4706,719.0829,316.3058,650.3904,703.6537,248.6998,638.7632,690.6605,403.8466,620.6404,722.7835,416.2239,617.5184,760.3033,398.1678,626.1278,799.7767,374.9795,631.2995,685.7261,337.1644,606.4107,834.8669,339.343,620.9934,819.4083,347.5469,645.523,848.1334,356.5438,498.4874,698.4639,371.3268,637.7817,673.2694,433.3049,470.7128,707.9303,452.8324,444.4687,676.6123,377.1859,370.8507,737.2457,437.6622,409.629,731.9924,430.6707,352.7672,703.4115,455.6927,302.9601,693.6641,391.0417,282.7681,666.0589,489.4929,215.8255,613.3008,440.3307,209.8857,707.6892,438.8821,256.9645,604.7092,496.4903,166.0379,582.2635,525.8381,85.547,606.4584,521.3459,105.7455,615.0229,494.2785,57.7416,625.0934,498.1225,90.6553,594.4101,545.5033,11.3189,596.1742,505.0817,-3.8193,604.802,501.9849,28.1967,599.2864,529.7363,40.7751,65.8666,-256.7096,179.7569,54.7513,-222.9911,179.5489,-71.0191,145.2956,178.2247,-83.1445,178.3213,178.1259,64.2257,-171.1313,225.9365,-32.5399,110.5994,226.324,249.3302,208.043,229.3717,346.7865,-76.0955,229.8219 +35,0.34,725.2939,80.0281,190.8267,643.0055,312.6289,177.2074,864.1872,201.0654,220.2393,829.3497,307.1456,218.6727,700.9654,224.2466,618.732,712.379,192.7569,615.7681,700.9654,224.2466,618.732,859.9414,263.7122,610.3407,869.4639,258.093,476.4075,673.4391,190.8381,430.6066,696.1334,278.6034,637.4759,710.5312,343.1161,643.882,691.7772,304.2878,618.4008,718.8627,316.4868,650.2664,703.5842,248.7727,638.7006,690.3616,403.9052,620.4618,722.3899,416.3602,617.3665,759.9368,398.3797,625.9551,799.4553,375.2943,631.1555,685.4302,337.2274,606.3304,834.5123,339.6336,620.8177,819.1367,347.9148,645.3983,847.7495,356.8219,498.3309,698.1528,371.4252,637.66,672.7312,433.5328,470.6665,707.4276,453.0501,444.4355,675.8066,377.5569,370.7859,736.643,437.8422,409.5065,731.2863,430.9386,352.6638,702.7118,456.0968,302.987,692.7134,391.4657,282.7297,665.4908,490.0935,215.9325,612.5607,441.1196,209.8691,706.8856,439.2766,257.0021,604.2219,497.3555,166.0862,581.9421,526.8807,85.6218,606.1249,522.2787,105.8231,614.6292,495.2581,57.7684,624.696,499.0205,90.7174,594.2915,546.6517,11.4745,595.8548,506.2461,-3.7808,604.429,503.0553,28.2193,599.0664,530.8209,40.8952,65.8666,-256.7115,179.7526,54.7487,-222.9911,179.5662,-71.0199,145.2946,178.2278,-83.149,178.3216,178.1311,64.2262,-171.133,225.9373,-32.5423,110.5991,226.3256,249.3266,208.0414,229.3727,346.7832,-76.0889,229.8214 +36,0.35,725.1498,79.9741,190.8535,642.7507,312.5386,177.1775,863.9897,201.0126,220.2229,829.1077,307.08,218.6406,700.8896,224.3071,618.7141,712.3846,192.8376,615.7642,700.8896,224.3071,618.7141,859.8029,264.1034,610.2608,869.2979,258.3541,476.3347,673.3409,190.7159,430.6513,695.9744,278.6886,637.4228,710.2346,343.2379,643.7852,691.5441,304.3631,618.3428,718.5819,316.6566,650.1213,703.478,248.8635,638.6479,689.9343,403.9777,620.3949,722.0008,416.4817,617.2262,759.5605,398.602,625.8081,799.1234,375.5851,630.9981,685.162,337.2993,606.2497,834.2443,339.974,620.6816,818.8425,348.2628,645.272,847.4153,357.0793,498.2004,697.814,371.5121,637.5524,672.2015,433.7406,470.6688,706.9041,453.2557,444.4199,674.9219,378.0134,370.649,736.0321,438.0348,409.4064,730.5823,431.1912,352.5636,701.9909,456.4482,303.0053,691.7827,391.8837,282.6761,664.9128,490.6917,216.0542,611.7999,441.9147,209.8936,706.0744,439.6848,257.062,603.7172,498.2295,166.1511,581.6129,527.9324,85.7297,605.754,523.2186,105.9306,615.0184,496.3791,58.0402,624.2518,499.9266,90.7955,594.1561,547.8074,11.6283,595.561,507.418,-3.7333,604.4261,504.2623,28.3601,598.8394,531.8775,41.0323,65.8687,-256.714,179.7561,54.7456,-222.9906,179.5623,-71.0148,145.298,178.2247,-83.1467,178.3192,178.1296,64.2214,-171.1337,225.9377,-32.5455,110.5918,226.325,249.3291,208.0426,229.3763,346.7872,-76.0961,229.82 +37,0.36,725.0092,79.8984,190.8961,642.5235,312.4521,177.1517,863.8165,200.9691,220.2113,828.8909,307.0198,218.6214,700.8225,224.3665,618.6929,712.4016,192.9299,615.7612,700.8225,224.3665,618.6929,859.6378,264.4659,610.1768,869.1192,258.6201,476.2662,673.2388,190.6022,430.6921,695.8026,278.8116,637.3678,709.9692,343.3596,643.6939,691.3466,304.4269,618.2793,718.3739,316.789,650.0428,703.391,248.9466,638.5778,689.6463,404.0321,620.2195,721.6282,416.6135,617.0847,759.1986,398.8189,625.6538,798.7875,375.8813,630.8433,684.9008,337.3567,606.1692,833.9754,340.3322,620.5676,818.5715,348.6149,645.1425,847.0585,357.34,498.0749,697.4879,371.61,637.4491,671.6724,433.9483,470.6892,706.3566,453.5146,444.3953,674.1127,378.3776,370.61,735.4288,438.2138,409.3297,729.6801,431.3682,352.4398,701.2542,456.8137,303.0457,690.85,392.3051,282.6611,664.291,491.2958,216.1743,611.0306,442.7063,209.927,705.3047,440.1176,257.12,603.1947,499.1001,166.2352,581.277,528.9835,85.8501,605.3747,524.1678,106.0282,614.6213,497.3391,58.1847,623.793,500.8302,90.8849,593.9828,548.9561,11.812,595.2171,508.6039,-3.6503,604.066,505.3205,28.4511,598.5801,532.9663,41.1794,65.8657,-256.7126,179.7568,54.7513,-222.9906,179.5486,-71.0138,145.3012,178.219,-83.1481,178.3203,178.1284,64.2268,-171.132,225.935,-32.5323,110.6009,226.317,249.3296,208.0411,229.3728,346.7872,-76.0988,229.8177 +38,0.37,724.8901,79.8443,190.9494,642.2879,312.3667,177.1298,863.6346,200.9342,220.2033,828.6845,306.9648,218.6121,700.8107,224.4683,618.6588,712.3945,193.0093,615.7654,700.8107,224.4683,618.6588,859.4612,264.8564,610.0914,868.9392,258.8611,476.1924,673.1603,190.4817,430.7314,695.6354,278.9307,637.2934,709.7068,343.4761,643.613,691.1391,304.4869,618.2137,718.1332,316.917,649.9797,703.243,249.0451,638.545,689.2972,404.1162,620.1157,721.2448,416.7401,616.9534,758.8338,399.0309,625.4874,798.4637,376.1671,630.6926,684.6201,337.4314,606.0997,833.7146,340.6772,620.433,818.2859,348.9496,645.0234,846.7232,357.576,497.9633,697.1763,371.711,637.3524,671.135,434.1482,470.7125,705.8398,453.7332,444.3898,673.2997,378.7144,370.5712,734.8304,438.3946,409.2392,729.1079,431.6665,352.415,700.5387,457.22,303.1001,689.9131,392.7278,282.6399,663.6775,491.9003,216.3032,610.2515,443.4824,209.9833,704.5388,440.5255,257.1717,602.6427,499.9555,166.3372,580.7697,530.1133,86.1224,604.9502,525.1112,106.1682,614.1714,498.3319,58.3354,623.3102,501.7432,90.994,593.7708,550.1127,12.0248,594.8147,509.7856,-3.5458,603.6463,506.408,28.5664,598.2732,534.0419,41.3659,65.8614,-256.7108,179.7583,54.7397,-222.9926,179.5573,-71.0184,145.3065,178.2233,-83.1466,178.3197,178.1277,64.2267,-171.1341,225.9387,-32.5328,110.6036,226.3198,249.3331,208.0393,229.3722,346.7878,-76.0848,229.819 +39,0.38,724.7906,79.7945,191.0018,642.067,312.2856,177.1118,863.4902,200.9019,220.2057,828.4768,306.9202,218.6096,700.8391,224.5912,618.6066,712.3817,193.0842,615.7728,700.8391,224.5912,618.6066,859.2112,265.2363,609.997,868.795,259.1127,476.1555,673.1312,190.3593,430.7807,695.4489,279.0645,637.2169,709.4421,343.6069,643.5356,690.9363,304.5623,618.1439,717.912,317.0509,649.9059,703.0908,249.137,638.5052,688.9749,404.1691,620.0013,720.9026,416.8548,616.8204,758.4987,399.2317,625.3336,798.138,376.4415,630.5235,684.3711,337.477,606.0117,833.4448,341.0088,620.3006,818.0183,349.2663,644.8891,846.3532,357.8327,497.8372,696.8898,371.8091,637.243,670.6245,434.3744,470.7349,705.3205,453.944,444.386,672.5154,379.087,370.5513,734.2125,438.5761,409.1833,728.3892,431.9227,352.3568,699.7814,457.5826,303.1363,688.9864,393.1578,282.6093,663.0563,492.5032,216.4484,609.4771,444.2699,210.0435,703.7281,440.9553,257.2309,602.0726,500.818,166.4644,580.4142,531.0764,86.1714,604.4803,526.046,106.3476,613.6369,499.362,58.4593,622.7766,502.672,91.1027,593.4911,551.2762,12.2564,594.3981,510.9939,-3.4359,603.2315,507.5107,28.687,597.8882,535.1438,41.5787,65.8675,-256.7113,179.752,54.7386,-222.9931,179.5489,-71.0291,145.3024,178.2271,-83.1534,178.3185,178.1303,64.2258,-171.131,225.9403,-32.5432,110.5933,226.3207,249.3313,208.0421,229.37,346.7878,-76.0943,229.8204 +40,0.39,724.7011,79.7469,191.0565,641.8536,312.193,177.0865,863.3393,200.8847,220.2139,828.2805,306.8784,218.6087,700.5257,224.5556,618.6717,712.3785,193.1582,615.8751,700.5257,224.5556,618.6717,858.9549,265.5905,609.9167,868.6182,259.334,476.0968,673.1099,190.2497,430.8202,695.2704,279.1486,637.1742,709.1861,343.7192,643.4438,690.7261,304.6338,618.0712,717.7359,317.1953,649.7911,703.0745,249.2267,638.4194,688.5978,404.2587,619.9653,720.56,416.9778,616.6811,758.1824,399.422,625.1589,797.8309,376.7131,630.3713,684.1439,337.5414,605.9137,833.2855,341.3877,620.2108,817.7589,349.5749,644.7612,846.0918,358.1145,497.7166,696.6141,371.8976,637.1226,670.138,434.5968,470.7581,704.859,454.1136,444.3823,671.7385,379.4106,370.5069,733.6394,438.7305,409.0997,727.7159,432.1758,352.3165,699.0796,457.9547,303.1772,688.0867,393.6038,282.585,662.428,493.1,216.5927,608.7213,445.0402,210.1122,702.9622,441.3908,257.2886,601.4766,501.6525,166.5954,579.817,532.1829,86.4617,603.9539,526.9769,106.5194,612.2652,500.2335,58.217,622.238,503.595,91.2465,593.1046,552.4676,12.5127,593.9431,512.2215,-3.2872,602.7652,508.6155,28.8477,597.4619,536.2275,41.8093,65.8636,-256.7139,179.7453,54.7542,-222.993,179.5504,-71.0342,145.2989,178.2302,-83.157,178.3189,178.1313,64.2231,-171.1292,225.9331,-32.5477,110.595,226.32,249.3243,208.0426,229.3746,346.7863,-76.0863,229.8196 +41,0.4,724.6415,79.693,191.1025,641.6495,312.1215,177.0779,863.2094,200.8649,220.209,828.0946,306.8388,218.5998,700.3166,224.538,618.6895,712.3367,193.225,615.8207,700.3166,224.538,618.6895,858.7828,265.9053,609.8505,868.4655,259.5362,476.0496,673.0977,190.129,430.8477,695.0982,279.2622,637.1059,708.9475,343.8228,643.3486,690.5237,304.6914,618.0033,717.5208,317.324,649.7017,702.999,249.3452,638.3542,688.3217,404.3418,619.7856,720.2562,417.0994,616.5569,757.8728,399.6097,625.0048,797.5504,376.975,630.2149,683.9102,337.6279,605.8254,832.9812,341.5972,620.0496,817.5114,349.8639,644.626,845.7115,358.2257,497.5995,696.3461,372.0005,637.0165,669.6802,434.8246,470.7695,704.3925,454.3791,444.3702,671.0818,379.6945,370.5567,733.101,438.8972,409.0488,727.1427,432.539,352.2634,698.4095,458.3507,303.2013,687.2108,394.0215,282.5598,661.7959,493.6884,216.7122,607.9616,445.7841,210.174,702.1808,441.813,257.3315,600.8542,502.4788,166.7489,579.3699,533.0854,86.5688,603.4074,527.9052,106.6969,611.6522,501.2563,58.3555,621.6401,504.5146,91.4095,592.6827,553.645,12.8178,593.4727,513.4773,-3.1241,602.2817,509.7409,29.0128,596.9903,537.3111,42.0727,65.8673,-256.7153,179.7547,54.7599,-222.9915,179.5449,-71.0272,145.2935,178.2243,-83.1519,178.3202,178.1279,64.227,-171.131,225.9366,-32.5459,110.5939,226.3171,249.3263,208.0359,229.3728,346.7874,-76.0933,229.8205 +42,0.41,724.5865,79.6473,191.136,641.454,312.0395,177.063,863.093,200.8498,220.2197,827.9146,306.8117,218.6045,700.3629,224.6619,618.6258,712.302,193.3114,615.8336,700.3629,224.6619,618.6258,858.5674,266.1928,609.7856,868.344,259.7368,476.0352,673.0707,190.0139,430.8657,694.9247,279.3721,637.0388,708.697,343.9587,643.2653,690.317,304.7607,617.9326,717.3019,317.4576,649.6241,702.5795,249.3544,638.3958,688.0641,404.4272,619.6593,719.9693,417.2217,616.4134,757.5955,399.8036,624.8443,797.2764,377.2205,630.0602,683.6676,337.7134,605.7407,832.7414,341.8656,619.9009,817.3466,350.1755,644.576,845.3981,358.3894,497.4657,696.0735,372.1197,636.9131,669.2581,435.0813,470.7723,703.984,454.6045,444.3558,670.2587,380.1758,370.457,732.5862,439.0415,408.9615,726.4962,432.7921,352.1589,697.7679,458.7642,303.2234,686.356,394.4778,282.5195,661.1561,494.3089,216.8449,607.2126,446.5391,210.251,701.4818,442.2684,257.3554,600.2196,503.2917,166.9088,578.7794,534.0654,86.7889,602.6611,528.9797,106.9833,611.0524,502.2645,58.5116,621.0121,505.4328,91.553,592.1813,554.8276,13.1385,592.9155,514.7148,-2.9414,601.7012,510.8774,29.2062,596.4315,538.396,42.3349,65.8666,-256.7096,179.7569,54.758,-222.9908,179.5516,-71.0164,145.2976,178.2205,-83.1541,178.3174,178.134,64.2231,-171.1307,225.9337,-32.5405,110.593,226.3228,249.3273,208.0389,229.3759,346.7838,-76.0956,229.8176 +43,0.42,724.5416,79.5923,191.1723,641.2682,311.9241,177.0537,862.9691,200.8466,220.2145,827.7452,306.7919,218.6103,700.2807,224.7478,618.5976,712.2651,193.4035,615.9139,700.2807,224.7478,618.5976,858.4061,266.4759,609.7526,868.1953,259.9128,476.0128,673.0332,189.902,430.888,694.7436,279.492,636.9674,708.4782,344.1144,643.1618,690.1156,304.8517,617.8618,717.0834,317.6183,649.5406,702.5304,249.4997,638.3154,687.8168,404.5366,619.5502,719.7073,417.3632,616.2645,757.3299,400.0024,624.683,797.0002,377.448,629.9015,683.4444,337.8213,605.6452,832.5226,342.1319,619.7824,817.0638,350.4123,644.3685,845.1178,358.5392,497.3362,695.8353,372.2444,636.7935,668.864,435.3673,470.7827,703.6129,454.7982,444.3163,669.6458,380.4498,370.4955,732.174,439.1827,408.9029,725.6907,432.8798,352.0521,697.1835,459.1937,303.2567,685.547,394.9433,282.4719,660.5139,494.921,216.9576,606.4779,447.267,210.3484,700.7709,442.7268,257.3908,599.5811,504.0968,167.0884,578.113,535.0245,87.0541,602.1508,529.7097,107.1297,610.3979,503.2662,58.6762,620.3502,506.3508,91.7222,591.6176,556.0106,13.5073,592.309,515.9482,-2.7275,600.5205,511.8888,29.2563,595.8359,539.4662,42.6437,65.8679,-256.7102,179.7596,54.7502,-222.9931,179.5481,-71.0164,145.2976,178.2205,-83.1441,178.3144,178.1282,64.2231,-171.1339,225.9392,-32.5425,110.5957,226.3194,249.3269,208.0421,229.3755,346.7837,-76.0947,229.8192 +44,0.43,724.503,79.5457,191.1915,641.0889,311.8211,177.0285,862.8851,200.8687,220.2238,827.5721,306.7834,218.6064,700.1738,224.8468,618.5703,712.2063,193.5048,615.9096,700.1738,224.8468,618.5703,858.2493,266.7567,609.7314,868.0404,260.0626,475.9769,672.9836,189.8041,430.9199,694.5735,279.6132,636.8855,708.2505,344.2624,643.0745,689.9234,304.9728,617.7836,716.8887,317.783,649.439,702.4015,249.6087,638.2355,687.5361,404.6554,619.4923,719.4587,417.4998,616.1253,757.0931,400.1996,624.5311,796.7377,377.6995,629.7452,683.2303,337.9499,605.5536,832.438,342.4288,619.7706,816.8608,350.6693,644.239,844.8475,358.6995,497.2263,695.6053,372.3903,636.6803,668.5309,435.6497,470.7827,703.2939,455.0609,444.2952,668.8927,380.9403,370.3686,731.699,439.3669,408.7612,725.2671,433.1889,351.9884,696.5911,459.6287,303.2823,684.7742,395.415,282.4125,659.9016,495.542,217.0804,605.7653,448.0155,210.4472,700.088,443.2075,257.3892,598.9207,504.8901,167.2851,577.3506,536.0325,87.4319,601.4749,530.5991,107.3786,609.724,504.2517,58.8481,619.6589,507.2569,91.9189,590.9492,557.1664,13.9006,591.6462,517.1931,-2.5125,599.833,512.9908,29.4779,595.1257,540.5446,42.9785,65.8673,-256.7141,179.7527,54.7483,-222.9926,179.554,-71.012,145.3,178.2205,-83.1471,178.3207,178.1296,64.2267,-171.1314,225.9332,-32.5357,110.5976,226.3186,249.33,208.0371,229.3727,346.7862,-76.0974,229.8203 +45,0.44,724.4836,79.5065,191.2127,640.929,311.7106,176.9884,862.7822,200.8812,220.2051,827.4045,306.7899,218.6059,700.0435,224.9454,618.528,712.1356,193.6205,615.9058,700.0435,224.9454,618.528,858.1126,267.0488,609.715,867.8984,260.1932,475.9585,672.9346,189.7357,430.9261,694.3978,279.749,636.7979,708.0374,344.4233,642.9764,689.7399,305.0962,617.6971,716.6852,317.9547,649.345,702.2431,249.7353,638.1714,687.3793,404.769,619.3032,719.2233,417.6598,615.9868,756.8415,400.3881,624.3618,796.5212,377.9391,629.6058,683.0255,338.0924,605.4567,832.3192,342.7606,619.6904,816.6269,350.9469,644.1293,844.5882,358.8543,497.1113,695.3818,372.5608,636.5714,668.1965,435.9554,470.767,703.0146,455.2847,444.2549,668.2687,381.3282,370.3271,731.2795,439.5391,408.652,724.766,433.4679,351.9047,696.0292,460.035,303.2649,684.0618,395.8997,282.351,659.2941,496.1593,217.194,605.0674,448.7324,210.5636,699.4868,443.7139,257.3796,598.2585,505.6628,167.4811,576.6063,536.9306,87.7363,600.7575,531.4893,107.637,608.9292,505.2476,59.0414,618.9235,508.1362,92.0998,590.2147,558.3171,14.3205,590.9248,518.402,-2.2346,599.0612,514.0868,29.7154,594.4126,541.5762,43.3292,65.8696,-256.715,179.7554,54.7492,-222.9906,179.5537,-71.012,145.3,178.2205,-83.149,178.3206,178.1298,64.225,-171.1315,225.9349,-32.5327,110.5996,226.3131,249.3325,208.0403,229.3737,346.7845,-76.0978,229.8223 +46,0.45,724.4774,79.4572,191.2176,640.7493,311.607,176.9523,862.7059,200.908,220.2105,827.3801,306.8304,218.6584,699.9114,225.061,618.488,712.0241,193.7408,615.8143,699.9114,225.061,618.488,857.9629,267.3151,609.6844,867.7516,260.3397,475.9462,672.8782,189.6811,430.9337,694.2059,279.8836,636.7166,707.806,344.61,642.8789,689.5461,305.2286,617.6083,716.4728,318.1317,649.2553,702.0876,249.8754,638.0963,687.1061,404.9302,619.2316,719.0194,417.8291,615.8457,756.6097,400.5962,624.2045,796.2777,378.1804,629.4512,682.803,338.2514,605.3503,831.898,342.882,619.4404,816.412,351.2058,644.0113,844.364,358.9855,497.0113,695.1733,372.7302,636.4406,667.8932,436.2596,470.758,702.751,455.5131,444.2097,667.8337,381.6651,370.3656,730.9548,439.7212,408.5914,724.3033,433.7878,351.8263,695.5642,460.4884,303.2919,683.4111,396.4087,282.2993,658.7029,496.7857,217.3003,604.4047,449.4402,210.6895,698.8848,444.216,257.3459,597.5882,506.4215,167.697,575.9226,537.7786,87.9488,600.0033,532.3459,107.9033,608.0991,506.2367,59.2425,618.1483,509.0182,92.2913,589.4073,559.4288,14.7424,590.1209,519.5912,-1.9498,598.6363,515.222,30.0806,593.6187,542.5817,43.6922,65.8684,-256.7145,179.754,54.7446,-222.9968,179.5561,-71.0205,145.2955,178.2235,-83.1481,178.321,178.1294,64.219,-171.1294,225.9332,-32.5334,110.5926,226.3146,249.3327,208.0446,229.3764,346.7814,-76.0995,229.823 +47,0.46,724.4868,79.398,191.2333,640.5878,311.4825,176.908,862.624,200.937,220.2099,827.2181,306.8445,218.6433,699.7765,225.2064,618.4415,711.9164,193.8897,615.8049,699.7765,225.2064,618.4415,857.7734,267.5716,609.6557,867.5832,260.4836,475.9222,672.8007,189.6373,430.9324,694.0124,280.016,636.6239,707.5881,344.7906,642.766,689.3403,305.3745,617.5232,716.2551,318.3333,649.1628,701.9277,250.0361,638.0311,686.9906,405.1163,619.0826,718.8162,418.0059,615.6913,756.3899,400.8111,624.0527,796.1069,378.4109,629.3023,682.6009,338.418,605.2487,831.7938,343.1629,619.4327,816.1988,351.4492,643.8904,844.1202,359.1171,496.8965,694.962,372.9144,636.3226,667.6113,436.584,470.7403,702.5187,455.7634,444.1743,667.314,382.105,370.308,730.6403,439.9157,408.4952,723.7017,434.0713,351.7083,695.1023,460.9378,303.2727,682.8047,396.8979,282.2321,658.1315,497.4034,217.3799,603.7449,450.1543,210.8245,698.3995,444.6532,257.2306,596.9229,507.1722,167.9123,575.1317,538.6492,88.2875,599.1071,533.3608,108.2332,607.2183,507.1291,59.4487,617.3631,509.8542,92.4995,588.5618,560.4954,15.158,589.2377,520.7444,-1.6548,597.3554,516.1483,30.2511,592.7942,543.5809,44.0511,65.8636,-256.7176,179.7569,54.7428,-222.9958,179.5604,-71.0184,145.2967,178.2216,-83.1426,178.3144,178.1271,64.2224,-171.1282,225.9299,-32.5361,110.5859,226.318,249.3304,208.0371,229.3719,346.7832,-76.0982,229.8205 +48,0.47,724.5214,79.331,191.2359,640.5279,311.2605,176.8364,862.5465,200.9799,220.2035,827.0765,306.8554,218.627,699.619,225.3474,618.411,711.7971,194.0424,615.7986,699.619,225.3474,618.411,857.6009,267.8218,609.6099,867.4135,260.6198,475.9115,672.7111,189.6224,430.9258,693.8181,280.1352,636.5234,707.3824,344.9643,642.6608,689.1548,305.5374,617.4218,716.0515,318.5331,649.0627,701.7147,250.1787,637.9667,686.8203,405.2858,618.9489,718.6166,418.1814,615.5227,756.1776,401.0221,623.8815,795.8871,378.6328,629.1418,682.397,338.5967,605.1351,831.6788,343.4694,619.356,815.9745,351.7108,643.765,843.8876,359.2346,496.7863,694.7357,373.1067,636.1998,667.3574,436.913,470.7078,702.3246,456.0366,444.1329,666.8209,382.509,370.2239,730.3052,440.1461,408.3823,723.4691,434.446,351.6602,694.6848,461.3915,303.2435,682.2169,397.4214,282.1592,657.5804,498.0264,217.4548,603.0916,450.9492,210.9778,697.7083,445.2029,257.218,596.2659,507.9415,168.1403,574.3256,539.5346,88.6115,598.4767,534.0266,108.4278,606.3792,508.0094,59.6798,616.5077,510.6975,92.6833,587.6557,561.5255,15.5633,588.2689,521.8324,-1.3564,596.4289,517.1344,30.5277,591.9128,544.5356,44.4052,65.863,-256.7128,179.7492,54.7475,-222.9915,179.554,-71.0184,145.2967,178.2216,-83.142,178.3141,178.1273,64.2264,-171.1307,225.9286,-32.5457,110.5921,226.325,249.3254,208.0389,229.3726,346.78,-76.0935,229.8208 +49,0.48,724.5717,79.278,191.2522,640.4375,311.0887,176.7689,862.4862,201.0256,220.2015,826.9287,306.8782,218.6129,699.4504,225.4772,618.368,711.6605,194.1873,615.7859,699.4504,225.4772,618.368,857.4154,268.056,609.5786,867.2446,260.757,475.8987,672.6282,189.6402,430.9254,693.6163,280.2491,636.4097,707.1545,345.1888,642.5457,688.957,305.7104,617.3196,715.8578,318.7231,648.9506,701.4968,250.3352,637.9005,686.6431,405.4673,618.8089,718.434,418.355,615.3729,755.9743,401.2287,623.7227,795.6554,378.8689,628.9858,682.2134,338.789,605.015,831.262,343.5863,619.0989,815.7637,351.9533,643.6506,843.6559,359.3515,496.6727,694.5389,373.296,636.0612,667.1205,437.254,470.6663,702.139,456.2667,444.0758,666.406,382.9608,370.1363,730.0268,440.4233,408.2818,723.1274,434.7754,351.5535,694.3246,461.9003,303.2151,681.3843,397.9149,282.0695,657.0385,498.6541,217.5059,602.4902,451.5737,211.1232,697.1995,445.71,257.1416,595.627,508.7069,168.3629,573.5187,540.253,88.9217,597.7072,534.8541,108.6941,605.3875,508.9101,59.9044,615.7083,511.5089,92.8863,586.6963,562.5292,15.9554,587.2632,522.8872,-1.0254,595.8193,518.1776,30.9229,591.0519,545.4731,44.7785,65.8671,-256.7136,179.7537,54.7543,-222.9909,179.5593,-71.0184,145.2967,178.2216,-83.1539,178.3193,178.1283,64.2218,-171.1264,225.9377,-32.5454,110.596,226.3248,249.3237,208.0389,229.3751,346.781,-76.1048,229.8209 +50,0.49,724.6335,79.2396,191.2657,640.304,310.99,176.7105,862.4369,201.0889,220.2211,826.795,306.8951,218.5859,699.298,225.6386,618.3134,711.5422,194.3422,615.8392,699.298,225.6386,618.3134,857.2028,268.2891,609.5438,867.0793,260.8814,475.8713,672.5602,189.661,430.9103,693.4348,280.3401,636.2842,706.954,345.379,642.4276,688.7648,305.8809,617.2229,715.645,318.9164,648.8439,701.3129,250.506,637.8196,686.4526,405.6488,618.6793,718.265,418.537,615.2202,755.769,401.4337,623.5535,795.5102,379.1237,628.8729,682.0186,338.9685,604.8878,831.2219,343.8557,619.0989,815.5604,352.2029,643.538,843.4323,359.4498,496.5635,694.3425,373.4811,635.9167,666.9013,437.5904,470.6143,701.9548,456.5581,444.0164,665.5905,383.262,370.0221,729.8156,440.6166,408.2086,722.653,435.0745,351.4192,693.9554,462.3157,303.16,680.9122,398.4363,281.9894,656.5185,499.2604,217.5608,601.8704,452.3049,211.2865,696.8256,446.1716,256.9795,594.9785,509.49,168.6226,572.5957,541.2009,89.4022,596.9304,535.6642,108.9687,604.4117,509.7899,60.1421,614.8566,512.3059,93.084,585.7191,563.4916,16.3286,586.1998,523.907,-0.685,594.7954,519.0947,31.1876,590.1447,546.403,45.1232,65.8613,-256.7197,179.7561,54.7474,-222.9933,179.5457,-71.0203,145.2976,178.2209,-83.1531,178.3186,178.1303,64.216,-171.1254,225.9286,-32.5452,110.5948,226.3214,249.3237,208.0404,229.3768,346.7816,-76.1015,229.8167 +51,0.5,724.6974,79.2005,191.2576,640.1492,310.897,176.6529,862.3854,201.1351,220.2179,826.5306,306.8824,218.5036,699.1428,225.8121,618.2562,711.3832,194.5043,615.7337,699.1428,225.8121,618.2562,857.0156,268.5279,609.5386,866.9103,261,475.8604,672.4924,189.7002,430.8785,693.234,280.4612,636.1736,706.7506,345.5596,642.3052,688.5875,306.0575,617.1126,715.4424,319.1216,648.7362,701.1464,250.6762,637.7383,686.2784,405.8206,618.5284,718.0753,418.721,615.064,755.5827,401.6376,623.3972,795.2491,379.2798,628.6755,681.8173,339.1489,604.765,831.0123,344.0539,618.9879,815.3664,352.4305,643.4114,843.2351,359.5552,496.462,694.1522,373.6753,635.7725,666.699,437.9279,470.5659,701.7902,456.8254,443.9545,665.2929,383.6072,370.0074,729.558,440.9017,408.1131,722.3552,435.4265,351.3141,693.5916,462.7964,303.0831,680.4784,398.9644,281.8987,656.022,499.876,217.6004,601.2537,453.0288,211.4597,695.965,446.7032,256.8861,594.3435,510.2487,168.8634,571.8627,542.0959,89.6585,596.1492,536.4868,109.2234,603.6303,510.8095,60.3179,614.0019,513.1064,93.2701,584.7056,564.4372,16.7029,585.1065,524.9304,-0.3621,593.7869,520.0254,31.4999,589.2233,547.3085,45.4613,65.8634,-256.7174,179.7513,54.7458,-222.9907,179.5618,-71.0201,145.2979,178.2202,-83.154,178.3182,178.1306,64.2157,-171.1294,225.9348,-32.5343,110.5988,226.3236,249.325,208.0421,229.3772,346.7895,-76.101,229.8215 +52,0.51,724.7921,79.1441,191.2739,640.0079,310.8052,176.5987,862.3719,201.198,220.2201,826.4138,306.9045,218.4962,698.9894,225.9644,618.1821,711.2504,194.6654,615.7032,698.9894,225.9644,618.1821,856.8004,268.7405,609.5107,866.7617,261.1256,475.8508,672.4344,189.7456,430.8449,693.0363,280.629,636.062,706.5433,345.7529,642.1859,688.4119,306.2243,617.0021,715.248,319.3072,648.6251,700.966,250.8376,637.6591,686.1099,405.9874,618.3816,717.8905,418.8998,614.8994,755.3897,401.8368,623.2334,795.0569,379.512,628.5577,681.6259,339.3246,604.6417,830.8157,344.2747,618.8972,815.1913,352.6633,643.2991,843.0522,359.6613,496.3495,693.9455,373.8593,635.6337,666.4969,438.2567,470.5154,701.621,457.107,443.8919,664.5728,383.7224,369.8745,729.3265,441.1892,408.0067,722.0827,435.8142,351.229,693.295,463.3207,303.046,680.0405,399.5257,281.8185,655.523,500.4818,217.6346,600.6708,453.7787,211.642,695.7485,447.3046,256.8499,593.7154,511.002,169.121,571.0439,542.8212,89.977,595.3553,537.2755,109.4934,602.4948,511.4892,60.5757,613.1676,513.8926,93.4743,583.64,565.3641,17.0436,584.0286,525.8845,-0.0054,592.7034,520.936,31.7626,588.261,548.1782,45.8005,65.8678,-256.7146,179.7544,54.7474,-222.9916,179.5531,-71.0138,145.3012,178.219,-83.1479,178.321,178.1305,64.2228,-171.1289,225.9377,-32.5363,110.5983,226.3196,249.33,208.0427,229.375,346.7855,-76.1006,229.8204 +53,0.52,724.9033,79.1139,191.278,639.8627,310.7013,176.545,862.3433,201.2681,220.2255,826.2889,306.9532,218.4991,698.8394,226.1057,618.1172,711.1292,194.8199,615.656,698.8394,226.1057,618.1172,856.5875,268.957,609.4881,866.6129,261.2361,475.8467,672.3844,189.794,430.7967,692.8493,280.7792,635.9406,706.333,345.93,642.0579,688.2231,306.3924,616.8889,715.0396,319.4849,648.5142,700.7826,250.9925,637.5651,685.8618,406.1663,618.3024,717.7148,419.0473,614.7435,755.1805,402.0254,623.0747,794.7542,379.6971,628.4006,681.4589,339.5004,604.5029,830.6376,344.4845,618.8141,815.0026,352.8979,643.2215,842.879,359.7701,496.2543,693.7641,374.0368,635.4887,666.2935,438.582,470.4692,701.4314,457.4168,443.8492,664.2443,384.221,369.7992,729.0671,441.5002,407.9129,721.7917,436.2054,351.1439,692.9316,463.7865,302.9925,679.6092,400.0698,281.7367,655.0241,501.1471,217.7029,600.0999,454.4544,211.8022,695.3182,447.8431,256.7574,593.0743,511.7408,169.352,570.1754,543.7691,90.3471,594.5805,538.0684,109.7532,601.4883,512.3131,60.7964,612.3052,514.6761,93.6646,582.5873,566.2355,17.3848,582.8937,526.8325,0.3062,591.2763,521.7059,31.9616,587.2847,549.0538,46.1223,65.8664,-256.7124,179.7576,54.7474,-222.9916,179.5531,-71.0184,145.3065,178.2233,-83.149,178.3216,178.1311,64.2173,-171.1311,225.9363,-32.5477,110.5978,226.3293,249.3242,208.0423,229.3787,346.7863,-76.0934,229.8187 +54,0.53,725.027,79.0813,191.2879,639.7186,310.6046,176.4995,862.3405,201.3536,220.2449,826.1718,307.0068,218.4896,698.6907,226.2315,618.0483,711.0006,194.9517,615.6142,698.6907,226.2315,618.0483,856.4227,269.173,609.5007,866.4656,261.351,475.8629,672.3376,189.8257,430.7361,692.6548,280.9101,635.8254,706.1292,346.0964,641.9409,688.0389,306.5344,616.7713,714.819,319.6545,648.4023,700.6028,251.1409,637.4707,685.7497,406.2841,618.0739,717.519,419.2092,614.5948,754.9787,402.1985,622.9384,794.5459,379.8811,628.2819,681.2537,339.6548,604.3735,830.2977,344.6512,618.6255,814.8748,353.1519,643.2027,842.7111,359.8774,496.1768,693.554,374.1959,635.3504,666.0649,438.903,470.4169,701.2582,457.6816,443.8019,663.9299,384.6232,369.7173,728.8369,441.8159,407.8471,721.5105,436.6427,351.0846,692.6329,464.3028,302.9832,679.2017,400.6061,281.6667,654.5095,501.7729,217.7601,599.5549,455.1118,211.942,695.0532,448.3551,256.5972,592.4134,512.4914,169.6111,569.1926,544.7107,90.8857,593.767,538.8774,110.0341,600.5788,513.1348,61.0157,611.4479,515.4714,93.868,581.4951,567.1198,17.7151,581.7525,527.7755,0.6251,590.2289,522.5947,32.2622,585.9702,549.8405,46.3896,65.8708,-256.7152,179.7533,54.756,-222.9924,179.5497,-71.0184,145.2967,178.2216,-83.149,178.3216,178.1311,64.2218,-171.1322,225.9368,-32.5449,110.594,226.3159,249.3322,208.0455,229.3775,346.783,-76.0941,229.8195 +55,0.54,725.1677,79.0574,191.2988,639.5995,310.5128,176.4521,862.3442,201.4316,220.255,826.0739,307.0493,218.4984,698.5403,226.3578,617.9745,710.8691,195.0775,615.5721,698.5403,226.3578,617.9745,856.1959,269.378,609.4988,866.3236,261.4729,475.8767,672.2919,189.852,430.6765,692.4528,281.0345,635.7092,705.9105,346.2527,641.8147,687.8568,306.6557,616.6542,714.6102,319.8045,648.3198,700.4174,251.2502,637.3799,685.5518,406.4301,617.9355,717.3194,419.3366,614.4442,754.7534,402.3689,622.7977,794.3099,380.0688,628.1776,681.0671,339.7905,604.2372,830.1909,344.9179,618.6115,814.6531,353.3313,643.0771,842.556,359.9655,496.1194,693.3434,374.3472,635.215,665.8556,439.1899,470.3613,701.0486,457.982,443.7792,663.7246,385.3014,369.5997,728.5795,442.171,407.7678,721.3895,437.1157,351.0881,692.2949,464.8004,302.9622,678.8452,401.1317,281.612,654.0137,502.4023,217.8159,599.015,455.7652,212.0982,694.4658,448.9509,256.5771,591.7927,513.1774,169.8488,568.3434,545.4409,91.2857,592.9914,539.6526,110.3047,599.6245,513.9705,61.2274,610.61,516.2675,94.0742,580.3906,568.0012,18.0364,580.6749,528.6735,0.9434,589.1953,523.4507,32.5183,584.9865,550.696,46.7054,65.8669,-256.7099,179.7583,54.7462,-222.9959,179.5496,-71.0184,145.2967,178.2216,-83.1466,178.3237,178.1284,64.2155,-171.1326,225.9368,-32.5416,110.5979,226.324,249.325,208.0515,229.3733,346.7834,-76.0923,229.8233 +56,0.55,725.3006,79.0257,191.3225,639.4842,310.4112,176.4126,862.3697,201.5215,220.2713,825.9699,307.1063,218.4943,698.3935,226.4712,617.8984,710.7493,195.1832,615.5201,698.3935,226.4712,617.8984,855.9495,269.5478,609.5068,866.1878,261.5861,475.9,672.27,189.8703,430.6071,692.2645,281.1505,635.6006,705.6805,346.3875,641.6975,687.6663,306.7834,616.5464,714.387,319.9312,648.2086,700.231,251.3539,637.2848,685.3367,406.5445,617.7981,717.1221,419.451,614.3016,754.5213,402.5228,622.673,794.0954,380.2359,628.0902,680.8719,339.9041,604.0979,829.8912,345.0179,618.537,814.4741,353.515,643.0165,842.3995,360.0725,496.089,693.1298,374.4762,635.0719,665.6026,439.4782,470.3261,700.8206,458.2787,443.7659,663.3812,385.6352,369.4945,728.3175,442.4775,407.7339,720.9382,437.5269,351.0161,691.9268,465.212,302.9269,678.4224,401.6697,281.5824,653.526,503.0248,217.8938,598.4645,456.4138,212.2477,694.0435,449.4674,256.5235,591.1834,513.9006,170.0885,567.5203,546.359,91.6104,592.2305,540.4326,110.591,598.7602,514.7946,61.4596,609.8079,517.0443,94.2709,579.3043,568.8821,18.3527,579.6068,529.5952,1.2476,588.1835,524.2989,32.7724,584.0201,551.5363,46.9768,65.8693,-256.7147,179.7497,54.7368,-222.9914,179.5619,-71.0182,145.2988,178.2191,-83.1538,178.3196,178.1302,64.2154,-171.1299,225.9381,-32.5399,110.6024,226.3228,249.3343,208.0492,229.3763,346.784,-76.0928,229.8225 +57,0.56,725.4589,79.0092,191.3319,639.3862,310.3205,176.3855,862.4119,201.6176,220.2756,825.8945,307.1718,218.4964,698.2369,226.5571,617.8228,710.6269,195.2836,615.4605,698.2369,226.5571,617.8228,855.73,269.7115,609.5254,866.0511,261.6926,475.9323,672.2476,189.8755,430.5453,692.0614,281.2318,635.4857,705.4413,346.514,641.5764,687.4813,306.8829,616.4317,714.164,320.0569,648.1039,700.0515,251.4093,637.2095,685.0668,406.6434,617.7328,716.8962,419.5696,614.1799,754.2869,402.67,622.5768,793.8649,380.3976,628.0184,680.6872,340.0092,603.9719,829.8152,345.2284,618.6179,814.101,353.7276,643.0549,842.2448,360.1721,496.066,692.9033,374.5812,634.9371,665.368,439.7489,470.3087,700.6055,458.5323,443.7644,663.0327,386.0303,369.4212,728.014,442.6623,407.7222,720.6418,437.9408,351.0028,691.5778,465.6776,302.9429,677.9793,402.1702,281.5671,653.0244,503.6161,217.9693,597.9203,457.0715,212.4129,693.6133,449.9645,256.4818,590.5718,514.6061,170.3292,566.9202,547.005,91.7342,591.3524,541.3539,110.9158,597.8813,515.6326,61.7079,609.0531,517.8276,94.4934,578.2191,569.7566,18.6734,578.5261,530.5164,1.5563,587.2109,525.1821,33.0414,583.0757,552.4106,47.2712,65.8717,-256.7158,179.7578,54.7492,-222.9918,179.547,-71.0122,145.2997,178.2213,-83.15,178.3212,178.1315,64.2234,-171.1311,225.94,-32.5321,110.5989,226.3114,249.3292,208.0471,229.3779,346.7839,-76.0891,229.8191 +58,0.57,725.6429,78.9825,191.3644,639.2894,310.2122,176.3599,862.4501,201.7211,220.2791,825.824,307.2283,218.4897,698.0945,226.6259,617.744,710.5038,195.3672,615.4117,698.0945,226.6259,617.744,855.4881,269.8658,609.562,865.9244,261.8095,475.9855,672.243,189.8749,430.4701,691.8612,281.3088,635.3803,705.1963,346.601,641.4755,687.2806,306.9613,616.325,713.9305,320.1661,648.0061,699.85,251.4968,637.1259,684.9019,406.7174,617.5602,716.6595,419.6585,614.0637,754.0428,402.7992,622.4698,793.6092,380.556,627.9655,680.4863,340.1017,603.8488,829.5947,345.3824,618.5796,813.673,353.8965,643.0068,842.1002,360.252,496.0542,692.6533,374.6759,634.8176,665.0993,439.9892,470.2939,700.3574,458.8359,443.7927,663.0012,386.566,369.3419,727.7161,442.955,407.7432,720.483,438.4009,351.0295,691.2014,466.1052,302.9794,677.5569,402.654,281.5599,652.5247,504.1613,218.0475,597.3652,457.7112,212.5699,693.2137,450.4708,256.4702,589.9471,515.2951,170.5919,565.9269,547.9547,92.3524,590.7511,541.9383,111.1558,597.051,516.4573,61.9274,608.2507,518.6091,94.7114,577.1562,570.6472,18.9918,577.4983,531.4405,1.8741,586.249,526.0725,33.306,582.4578,553.3447,47.6331,65.8615,-256.7139,179.7616,54.7475,-222.9915,179.554,-71.0245,145.3053,178.2219,-83.149,178.3216,178.1311,64.2168,-171.1324,225.941,-32.5339,110.5976,226.3153,249.3342,208.04,229.3702,346.7848,-76.0861,229.8206 +59,0.58,725.8195,78.9596,191.3963,639.1957,310.1298,176.3349,862.4837,201.8212,220.2843,825.7662,307.2916,218.4899,697.9439,226.6819,617.665,710.3885,195.438,615.3481,697.9439,226.6819,617.665,855.2679,270.0202,609.5884,865.7897,261.9204,476.0374,672.22,189.8387,430.3892,691.6735,281.4242,635.3126,704.9568,346.6891,641.3788,687.0969,307.0254,616.2288,713.6904,320.245,647.9388,699.6656,251.575,637.0421,684.5939,406.8317,617.4832,716.4138,419.7545,613.9765,753.7849,402.9128,622.3867,793.416,380.7553,627.9843,680.2709,340.1791,603.7397,829.3356,345.5307,618.5512,813.5092,353.9942,642.9106,841.9249,360.3405,496.0516,692.4083,374.7537,634.7164,664.8102,440.2174,470.2941,700.0375,459.1223,443.8371,663.0318,387.1538,369.3448,727.4163,443.2437,407.7592,720.16,438.6037,350.9973,690.8163,466.5463,303.046,677.0836,403.1334,281.5888,652.0032,504.747,218.174,596.8041,458.3261,212.7426,692.8276,450.9142,256.4142,589.3297,515.9558,170.8495,565.1301,548.7178,92.7001,590.0096,542.6793,111.4477,596.1656,517.3183,62.1775,607.5117,519.3898,94.9493,576.1382,571.4985,19.3331,576.4828,532.3599,2.1726,585.3304,526.9429,33.5978,581.2311,554.1118,47.9085,65.8602,-256.7084,179.7569,54.7466,-222.9937,179.5554,-71.0249,145.3029,178.2252,-83.1477,178.3214,178.1305,64.214,-171.1349,225.9354,-32.5471,110.5993,226.3221,249.3251,208.039,229.3711,346.784,-76.0885,229.8216 +60,0.59,725.9887,78.934,191.4286,639.1139,310.0226,176.3263,862.5244,201.9411,220.2789,825.7144,307.3564,218.4734,697.7872,226.7331,617.6002,710.2612,195.4974,615.2962,697.7872,226.7331,617.6002,855.0669,270.1691,609.6363,865.6548,262.0116,476.0962,672.2045,189.7943,430.3215,691.4343,281.4461,635.1837,704.7241,346.76,641.2963,686.9152,307.0928,616.1284,713.4661,320.3204,647.8647,699.4653,251.633,636.9561,684.3676,406.8704,617.3939,716.153,419.8305,613.9018,753.5128,403.0156,622.3091,793.1142,380.8509,627.8932,680.0845,340.2299,603.6395,829.1165,345.6642,618.5461,813.2632,354.1338,642.8958,841.7288,360.4349,496.0542,692.1566,374.8127,634.6308,664.4945,440.4283,470.3079,699.767,459.3483,443.8819,662.6348,387.4789,369.3326,727.0984,443.5039,407.7932,719.8032,438.9389,351.0168,690.4009,466.9548,303.1251,676.635,403.5869,281.6223,651.4614,505.3127,218.3175,596.2435,458.9116,212.9016,692.0035,451.3714,256.4598,588.798,516.5305,171.0267,564.5606,549.309,92.8041,589.2627,543.4099,111.7696,595.4103,518.1183,62.4235,606.7263,520.1398,95.1844,575.1592,572.3549,19.6825,575.5522,533.248,2.5053,584.4479,527.8134,33.885,580.3493,554.9248,48.2307,65.8665,-256.7125,179.7576,54.7579,-222.9926,179.5414,-71.0265,145.3044,178.223,-83.1481,178.321,178.1294,64.2217,-171.1487,225.9307,-32.543,110.598,226.3235,249.321,208.0444,229.3751,346.7806,-76.0835,229.8204 +61,0.6,726.1642,78.9099,191.4663,639.0338,309.9177,176.3164,862.5717,202.0426,220.2751,825.6641,307.4198,218.4428,697.6255,226.785,617.5292,710.1225,195.5462,615.2496,697.6255,226.785,617.5292,854.8665,270.314,609.6852,865.5237,262.1237,476.1503,672.1979,189.7621,430.2513,691.2305,281.4922,635.0807,704.4741,346.8257,641.2183,686.7146,307.1474,616.0292,713.2319,320.3886,647.7944,699.2557,251.6883,636.874,684.1815,406.8519,617.278,715.9102,419.8844,613.8226,753.2589,403.0996,622.2385,792.8588,380.9658,627.8568,679.8964,340.2628,603.5374,828.8873,345.8183,618.5588,812.9933,354.2714,642.8867,841.5333,360.5259,496.0553,691.9161,374.8707,634.5421,664.184,440.6385,470.3471,699.4309,459.5944,443.9382,662.2175,387.8103,369.3395,726.7322,443.7234,407.8338,719.1777,439.3483,351.151,689.9669,467.3517,303.2227,676.1624,404.0503,281.671,650.8989,505.8925,218.4813,595.676,459.4689,213.0664,691.6946,451.9073,256.563,588.084,517.2134,171.3534,563.8128,550.0422,93.1378,588.5267,544.1278,112.0512,594.5904,518.9341,62.6694,605.9849,520.8688,95.4256,574.2187,573.2068,20.0359,574.6229,534.137,2.7957,583.5915,528.6226,34.1585,579.4916,555.7465,48.5456,65.8616,-256.7117,179.7475,54.7485,-222.9955,179.5492,-71.0247,145.3032,178.2245,-83.1494,178.322,178.1282,64.2224,-171.1522,225.9328,-32.5422,110.5965,226.3242,249.3256,208.0407,229.3729,346.7861,-76.0921,229.8199 +62,0.61,726.3411,78.8912,191.4949,638.9669,309.8079,176.3199,862.6301,202.1507,220.2623,825.6338,307.4702,218.4229,697.4756,226.8251,617.4679,709.9855,195.5899,615.2132,697.4756,226.8251,617.4679,854.6518,270.4353,609.7468,865.3855,262.227,476.2236,672.1971,189.7328,430.1816,691.0499,281.5811,635.0419,704.2424,346.8702,641.1393,686.5273,307.1891,615.9493,713.009,320.4489,647.7131,699.0667,251.7502,636.8115,683.8945,406.9027,617.2197,715.6423,419.9428,613.753,752.9863,403.1889,622.1898,792.5906,381.069,627.8235,679.6835,340.2936,603.4432,828.5308,345.9,618.4572,812.7355,354.4044,642.9241,841.3337,360.6068,496.0727,691.6768,374.9031,634.4493,663.8539,440.8278,470.3759,699.1289,459.8093,443.9899,661.4716,387.9564,369.3239,726.3247,443.9171,407.8842,718.7578,439.5964,351.2498,689.4958,467.7502,303.3742,675.6461,404.5086,281.7427,650.3157,506.451,218.6444,595.1121,460.0111,213.2336,690.949,452.336,256.5865,587.4348,517.8215,171.6118,562.8166,550.9227,93.7418,587.7986,544.8217,112.3454,594.0266,519.8589,62.895,605.2478,521.5996,95.6502,573.3138,574.0555,20.3741,573.7665,535.0179,3.0908,582.7652,529.4407,34.4319,578.681,556.5451,48.8531,65.8676,-256.7139,179.7585,54.738,-222.9968,179.5502,-71.0184,145.2967,178.2216,-83.1432,178.3148,178.1279,64.2215,-171.1547,225.9161,-32.5422,110.593,226.3195,249.3279,208.0361,229.3748,346.7847,-76.09,229.8207 +63,0.62,726.5062,78.8635,191.5249,638.8992,309.6861,176.3217,862.6744,202.2457,220.2639,825.5887,307.5193,218.4084,697.3216,226.8704,617.4173,709.8574,195.6474,615.1784,697.3216,226.8704,617.4173,854.4376,270.5354,609.7969,865.2502,262.3052,476.2892,672.1827,189.7056,430.1201,690.8503,281.6275,634.9625,704.0011,346.9101,641.0552,686.3287,307.2168,615.8681,712.7846,320.5081,647.6547,698.8793,251.7905,636.7469,683.6486,406.9156,617.1218,715.3763,419.9871,613.6803,752.7172,403.273,622.1389,792.3281,381.1743,627.8032,679.4775,340.3264,603.3519,828.2855,346.023,618.4684,812.4696,354.5231,642.9266,841.1349,360.6896,496.1036,691.4217,374.9364,634.3546,663.547,441.0527,470.5013,698.7902,460.0776,444.0616,661.3172,388.4205,369.3617,725.9482,444.0613,407.9548,718.6243,439.736,351.3496,689.0025,468.1545,303.5388,675.1115,404.9525,281.8535,649.7416,506.9704,218.7976,594.527,460.5519,213.3721,690.6547,452.8503,256.7441,586.7856,518.4224,171.8446,562.0698,551.5407,94.0837,587.0688,545.5138,112.6164,593.05,520.4809,63.1556,604.5304,522.3456,95.903,572.4931,574.8724,20.6941,572.9304,535.8779,3.3675,581.9678,530.2584,34.696,577.8607,557.3325,49.1648,65.8606,-256.7191,179.7488,54.7484,-222.9956,179.5482,-71.0164,145.2976,178.2205,-83.1427,178.3155,178.1284,64.2245,-171.1603,225.9167,-32.5355,110.5931,226.3176,249.3346,208.0438,229.3724,346.7888,-76.0946,229.8194 +64,0.63,726.675,78.8641,191.548,638.8721,309.4643,176.3654,862.7121,202.3458,220.2534,825.6554,307.6057,218.4625,697.1772,226.8912,617.3683,709.7287,195.6812,615.1315,697.1772,226.8912,617.3683,854.2358,270.637,609.8678,865.115,262.3739,476.3663,672.1497,189.6641,430.0526,690.6156,281.6068,634.8367,703.7559,346.9507,640.9853,686.1485,307.2496,615.7827,712.562,320.5482,647.5913,698.6946,251.8278,636.6833,683.4094,406.923,617.0358,715.1422,420.0311,613.6097,752.456,403.345,622.0871,792.0603,381.2647,627.7749,679.2859,340.3529,603.2667,828.0213,346.1433,618.4888,812.2255,354.6269,642.931,840.9826,360.7553,496.1316,691.1843,374.9648,634.2695,663.2131,441.2766,470.5732,698.4409,460.3234,444.1416,660.7313,388.7739,369.4145,725.5382,444.2428,408.0367,718.1685,439.9726,351.4931,688.4707,468.5487,303.7874,674.5434,405.3741,281.9778,649.1176,507.5052,218.9715,593.8924,461.0868,213.5218,689.9028,453.2745,256.8489,586.1253,519.0052,172.0721,561.5982,552.1628,94.055,586.3704,546.2037,112.8899,592.3403,521.2372,63.3781,603.8156,523.0531,96.1302,571.683,575.6713,21.0279,572.1282,536.7195,3.648,581.1898,531.058,34.9244,577.0793,558.0946,49.4268,65.8636,-256.7176,179.7569,54.7398,-222.9927,179.5574,-71.0184,145.2967,178.2216,-83.1494,178.3224,178.13,64.2274,-171.1572,225.9092,-32.5362,110.5964,226.3186,249.3283,208.0387,229.3787,346.7907,-76.0938,229.819 +65,0.64,726.8221,78.8396,191.5708,638.7925,309.3744,176.3761,862.7559,202.4319,220.253,825.5993,307.6489,218.4585,697.0163,226.9005,617.3162,709.5908,195.7012,615.0969,697.0163,226.9005,617.3162,854.0354,270.7138,609.9225,864.989,262.4334,476.4399,672.1015,189.6249,429.9893,690.4086,281.6217,634.7546,703.5285,346.9782,640.9273,685.963,307.2906,615.6401,712.3592,320.5864,647.5366,698.5074,251.8469,636.6205,683.1887,406.9284,616.9667,714.9077,420.0525,613.558,752.2087,403.3908,622.0273,791.8129,381.3362,627.7537,679.1064,340.3745,603.1912,827.8356,346.2476,618.5209,811.9777,354.7166,642.9445,840.827,360.7966,496.1657,690.9577,374.9967,634.1909,662.8716,441.4673,470.647,698.1093,460.5112,444.2252,660.2781,389.0187,369.4523,725.0782,444.3622,408.1302,717.3706,440.2992,351.7249,687.88,468.919,303.9753,673.9297,405.7727,282.1288,648.4827,508.0301,219.1691,593.2556,461.6181,213.6656,689.4255,453.7456,257.0472,585.4578,519.5904,172.3028,560.6561,553.0562,94.6333,585.6837,546.8865,113.1591,591.5502,522.0012,63.5854,603.1288,523.746,96.3438,570.9152,576.4604,21.2984,571.3741,537.5455,3.879,580.4448,531.8376,35.1678,576.3557,558.8768,49.7068,65.8723,-256.7188,179.7539,54.7395,-222.9946,179.5491,-71.0247,145.3032,178.2245,-83.1583,178.317,178.1302,64.2279,-171.163,225.9087,-32.5447,110.5925,226.3188,249.3375,208.0427,229.3737,346.7861,-76.0995,229.82 +66,0.65,726.9548,78.8234,191.5981,638.7241,309.2887,176.3777,862.7761,202.5274,220.254,825.537,307.6927,218.4526,696.8578,226.9043,617.2614,709.4615,195.7124,615.0529,696.8578,226.9043,617.2614,853.8286,270.7782,609.9922,864.8563,262.4957,476.5063,672.0509,189.5908,429.9336,690.2513,281.6824,634.7307,703.3301,347.0021,640.8659,685.8113,307.2798,615.6357,712.1394,320.6061,647.4964,698.3124,251.8622,636.5629,682.9933,406.9179,616.9163,714.6892,420.0752,613.5172,752.0022,403.4279,621.9933,791.6085,381.3995,627.7434,678.9316,340.3761,603.1198,827.6566,346.3344,618.5336,811.7547,354.7885,642.9576,840.6297,360.8533,496.2031,690.7659,375.0138,634.1255,662.5253,441.6351,470.7218,697.7327,460.71,444.3212,659.6674,389.3659,369.5515,724.6725,444.4607,408.2033,717.1752,440.3775,351.8409,687.2664,469.2897,304.173,673.2819,406.1916,282.2605,647.8372,508.5249,219.3711,592.6051,462.1271,213.8248,688.7697,454.1638,257.2225,584.7932,520.1818,172.5448,560.2244,553.5633,94.6136,585.0183,547.5461,113.4102,590.8774,522.7201,63.8063,602.4614,524.4459,96.5778,570.1788,577.2591,21.583,570.6688,538.3623,4.1257,579.7216,532.6114,35.3719,575.6755,559.6263,49.9747,65.868,-256.7172,179.7502,54.7376,-222.9964,179.5487,-71.0232,145.2909,178.2232,-83.1526,178.3193,178.1307,64.2252,-171.1596,225.8997,-32.5447,110.5929,226.3201,249.3246,208.0355,229.3764,346.7868,-76.0943,229.8186 +67,0.66,727.0674,78.8116,191.6079,638.645,309.182,176.381,862.8011,202.6237,220.2608,825.4641,307.7501,218.4526,696.7064,226.9036,617.2144,709.3304,195.7103,615.0239,696.7064,226.9036,617.2144,853.6572,270.8361,610.0538,864.7204,262.5533,476.5744,671.9867,189.5625,429.8738,690.0677,281.6875,634.6676,703.1375,347.0089,640.8098,685.6374,307.2835,615.5825,711.9404,320.6269,647.4586,698.1266,251.867,636.5121,682.8185,406.8968,616.8853,714.5092,420.0956,613.4918,751.8113,403.4807,621.9587,791.4044,381.453,627.731,678.7647,340.3862,603.0723,827.6084,346.4242,618.6644,811.5507,354.8377,642.9461,840.4688,360.8638,496.2221,690.5822,375.0024,634.0735,662.1176,441.795,470.7351,697.4058,460.852,444.4133,659.092,389.6194,369.6396,724.1741,444.5578,408.2781,716.632,440.5835,351.9542,686.6674,469.6621,304.3898,672.611,406.5792,282.4049,647.189,508.9997,219.6136,591.9115,462.6392,214.0014,688.1373,454.5803,257.4005,584.155,520.7458,172.765,559.603,554.2393,94.8766,584.3696,548.2018,113.6807,590.3284,523.4437,64.0237,601.8195,525.1204,96.8023,569.486,578.0333,21.8334,569.9801,539.1638,4.36,579.119,533.372,35.5965,575.0071,560.3832,50.2219,65.8548,-256.7156,179.7543,54.7445,-222.9964,179.5546,-71.0201,145.2979,178.2202,-83.145,178.3161,178.1275,64.2249,-171.1669,225.9041,-32.5439,110.5907,226.32,249.3292,208.0397,229.3776,346.7834,-76.097,229.8224 +68,0.67,727.1436,78.7811,191.631,638.5795,309.0924,176.3741,862.8044,202.7014,220.2581,825.3809,307.802,218.4644,696.5546,226.8938,617.1716,709.2,195.7225,614.9712,696.5546,226.8938,617.1716,853.4809,270.896,610.1039,864.5828,262.5881,476.6207,671.9387,189.5358,429.8103,689.8879,281.7042,634.6084,702.9582,347.0166,640.7715,685.4567,307.2574,615.5369,711.7577,320.633,647.4172,697.9545,251.8649,636.4645,682.5942,406.899,616.9102,714.3366,420.1021,613.4662,751.6302,403.5145,621.9324,791.2287,381.4947,627.7054,678.6066,340.3976,603.033,827.438,346.4707,618.6644,811.316,354.8308,642.8626,840.3063,360.8838,496.2378,690.42,375.0115,634.0337,661.7915,441.957,470.9301,697.0425,460.9918,444.4921,658.4974,389.8964,369.7271,723.7101,444.6672,408.3349,716.027,440.8395,352.0168,685.9254,469.9541,304.5453,671.8877,406.9347,282.5216,646.5037,509.4745,219.8587,591.2098,463.1251,214.1633,687.5068,454.8961,257.5089,583.4949,521.3081,173.0034,558.7635,555.0724,95.398,583.7636,548.8358,113.936,589.7245,524.1827,64.2171,601.2168,525.7847,97.034,568.836,578.7903,22.1029,569.3535,539.9659,4.5839,578.4884,534.1494,35.7921,574.3973,561.1328,50.4619,65.8693,-256.7147,179.7497,54.7445,-222.9944,179.5608,-71.0203,145.2976,178.2209,-83.1444,178.3158,178.1277,64.2395,-171.2094,225.9069,-32.5341,110.5925,226.3218,249.3377,208.036,229.3738,346.7856,-76.0925,229.8199 +69,0.68,727.2199,78.7623,191.6467,638.4877,309.0059,176.3515,862.7781,202.7803,220.2748,825.2963,307.8414,218.4673,696.4101,226.8814,617.1199,709.0819,195.7176,614.9398,696.4101,226.8814,617.1199,853.2983,270.9478,610.1501,864.4407,262.6422,476.6672,671.8661,189.5169,429.7434,689.7034,281.686,634.5381,702.7809,347.021,640.7381,685.3155,307.267,615.4848,711.5688,320.6319,647.3698,697.7916,251.8649,636.405,682.4849,406.8747,616.8304,714.1555,420.1108,613.4357,751.4457,403.5319,621.8966,791.218,381.569,627.7269,678.4299,340.3991,602.9918,827.251,346.5047,618.661,811.1312,354.8666,642.8538,840.1515,360.8941,496.2424,690.2516,375.0163,634.0031,661.4087,442.1237,471.0479,696.6556,461.1233,444.5656,657.9189,390.1628,369.8037,723.2321,444.8022,408.3516,715.3879,441.0681,352.0816,685.2222,470.2841,304.7358,671.1467,407.273,282.6368,645.8136,509.9261,220.1003,590.4875,463.6191,214.323,686.6755,455.2536,257.6619,582.8409,521.8317,173.215,558.3953,555.5544,95.3674,583.1546,549.4381,114.1729,589.139,524.8923,64.4414,600.631,526.4316,97.2617,568.2153,579.5423,22.3507,568.7466,540.7627,4.783,577.9186,534.8792,36.0061,573.7896,561.8293,50.6756,65.8667,-256.7127,179.759,54.7379,-222.9919,179.565,-71.0247,145.3032,178.2245,-83.1467,178.3192,178.1296,64.2279,-171.1945,225.9621,-32.5395,110.597,226.3208,249.3292,208.0403,229.3717,346.7857,-76.0925,229.82 +70,0.69,727.2578,78.7336,191.6528,638.3981,308.9239,176.3134,862.7328,202.8557,220.2893,825.1854,307.8927,218.4627,696.2842,226.8746,617.072,708.9721,195.7198,614.8959,696.2842,226.8746,617.072,853.1378,271.0008,610.1862,864.2936,262.6948,476.7029,671.7939,189.488,429.6833,689.5323,281.6772,634.4788,702.592,347.0034,640.6927,685.158,307.2505,615.4281,711.3753,320.6288,647.3262,697.6298,251.8504,636.3569,682.309,406.8518,616.8126,713.968,420.0977,613.3976,751.2488,403.541,621.855,790.8593,381.5806,627.6847,678.2462,340.4029,602.9628,826.8989,346.4984,618.5544,810.9434,354.9048,642.8557,840.0485,360.9866,496.2521,690.0704,375.0077,633.9622,661.003,442.2677,471.0799,696.2484,461.2633,444.6554,657.3252,390.4119,369.8795,722.7321,444.9469,408.3722,714.4196,441.4585,352.1735,684.491,470.5856,304.9053,670.3899,407.6201,282.7413,645.1107,510.3286,220.3186,589.769,464.0773,214.479,685.92,455.6853,257.9565,582.174,522.332,173.4192,557.5681,556.3323,95.8601,582.5285,550.0413,114.4167,588.5814,525.5684,64.6067,600.0278,527.0681,97.476,567.6172,580.2896,22.5924,568.217,541.5238,4.9745,577.3889,535.6184,36.2163,573.2002,562.5372,50.915,65.8697,-256.7159,179.7555,54.7389,-222.9992,179.5523,-71.0184,145.3065,178.2233,-83.1499,178.3223,178.129,64.2282,-171.1936,225.9607,-32.5448,110.6037,226.3262,249.3291,208.0447,229.3827,346.7856,-76.0913,229.8205 +71,0.7,727.2963,78.707,191.6481,638.2966,308.8468,176.2792,862.6771,202.9164,220.3065,825.0718,307.9358,218.4638,696.166,226.8564,617.0129,708.8795,195.7056,614.8462,696.166,226.8564,617.0129,852.9604,271.059,610.2129,864.166,262.7594,476.7426,671.7259,189.4577,429.6169,689.3685,281.6513,634.4129,702.4148,347.0036,640.6376,685.0108,307.2285,615.3578,711.2019,320.6186,647.2653,697.4739,251.8265,636.3011,682.1237,406.8082,616.7905,713.7628,420.0749,613.3605,751.0348,403.5489,621.8237,790.6516,381.6215,627.6642,678.054,340.3824,602.9226,826.7076,346.5537,618.5698,810.7093,354.9476,642.8566,839.8044,360.951,496.2791,689.8817,374.9956,633.9269,660.6554,442.415,471.2684,695.8257,461.3895,444.7218,656.5994,390.6611,369.9016,722.2146,445.1193,408.3847,714.0922,441.544,352.1739,683.7484,470.8737,305.0807,669.6254,407.9279,282.8535,644.3916,510.7736,220.5585,589.0571,464.4954,214.5795,685.2311,456.0742,258.142,581.4993,522.8079,173.5934,557.2113,556.7625,95.758,581.9215,550.6118,114.626,588.2311,526.3782,64.7954,599.4225,527.6672,97.6749,567.0632,581.0104,22.8303,567.7092,542.2729,5.1616,576.8368,536.303,36.3793,572.6349,563.2175,51.1277,65.8693,-256.7147,179.7496,54.7466,-222.9952,179.5551,-71.0138,145.3012,178.219,-83.1486,178.3213,178.1303,64.2282,-171.1966,225.9616,-32.5434,110.5968,226.3261,249.3315,208.0473,229.374,346.7905,-76.0942,229.8192 +72,0.71,727.2842,78.674,191.6429,638.1822,308.7709,176.2229,862.5964,202.9646,220.3252,824.9567,307.9654,218.4553,696.0482,226.826,616.9522,708.7781,195.6908,614.7966,696.0482,226.826,616.9522,852.807,271.1258,610.2364,864.0166,262.8258,476.7541,671.6616,189.4198,429.5552,689.2014,281.6124,634.3475,702.2083,346.9738,640.5878,684.8583,307.1979,615.3011,711.0234,320.5958,647.2131,697.3279,251.7916,636.2472,681.9073,406.76,616.7477,713.5344,420.0502,613.3239,750.7996,403.5662,621.7877,790.5449,381.6786,627.6766,677.8653,340.3477,602.8786,826.6374,346.6595,618.6812,810.53,355.028,642.9269,839.6764,361.0787,496.2864,689.6812,374.9637,633.8799,660.2198,442.5294,471.3587,695.3762,461.5223,444.7974,656.0643,390.8369,370.0316,721.6486,445.2692,408.4185,713.0838,441.8938,352.2932,682.9692,471.1627,305.2701,668.8798,408.2426,282.9595,643.6581,511.1566,220.7776,588.3384,464.8853,214.6912,684.506,456.3495,258.2722,580.8198,523.2831,173.7639,556.6092,557.3489,95.9419,581.3137,551.1609,114.834,587.5504,526.8699,64.9797,598.8481,528.2753,97.8582,566.5456,581.6921,23.035,567.2601,542.9937,5.3276,576.3449,536.9933,36.5673,572.1219,563.8625,51.3293,65.8733,-256.7147,179.7537,54.7389,-222.9918,179.5568,-71.0201,145.2979,178.2202,-83.1504,178.3226,178.1299,64.2306,-171.1989,225.9645,-32.5429,110.5949,226.3218,249.3269,208.0443,229.3782,346.784,-76.089,229.8198 +73,0.72,727.2589,78.6597,191.6184,638.0713,308.7167,176.1615,862.5139,203.0109,220.3324,824.8435,307.9994,218.4491,695.9163,226.806,616.8867,708.6747,195.6753,614.7419,695.9163,226.806,616.8867,852.6346,271.1771,610.248,863.8809,262.8941,476.77,671.5983,189.392,429.4856,689.0369,281.5869,634.2831,702.0024,346.9401,640.5359,684.6904,307.1538,615.2513,710.8457,320.5764,647.1719,697.1863,251.7688,636.1807,681.6712,406.7005,616.7153,713.2748,420.0402,613.2863,750.5549,403.5707,621.7552,790.3126,381.7194,627.6574,677.6844,340.3069,602.8259,826.444,346.7187,618.6836,810.3015,355.0613,642.9055,839.5031,361.1344,496.2914,689.4588,374.9201,633.8298,659.725,442.6004,471.3751,694.8599,461.685,444.9098,655.3268,391.0722,370.0504,721.0195,445.3732,408.4612,712.4134,442.1079,352.3908,682.2198,471.4281,305.4374,668.1517,408.5408,283.0589,642.9538,511.5549,221.0173,587.6281,465.2657,214.7883,683.6787,456.7057,258.4034,580.1666,523.7001,173.9151,555.8412,558.0629,96.3589,580.7275,551.6929,115.0241,587.0286,527.4984,65.1193,598.3088,528.843,98.0424,566.0629,582.3839,23.2291,566.8358,543.717,5.4681,575.8884,537.6503,36.6997,571.6116,564.5089,51.5271,65.8631,-256.7122,179.7557,54.7464,-222.9939,179.5526,-71.0228,145.3041,178.2233,-83.1548,178.3195,178.1291,64.2274,-171.1879,225.9579,-32.5414,110.5975,226.3186,249.3309,208.0408,229.3672,346.784,-76.0934,229.8177 +74,0.73,727.2208,78.6424,191.6006,637.9472,308.6764,176.1189,862.4183,203.034,220.3414,824.7042,308.0194,218.4451,695.7781,226.7791,616.832,708.552,195.6472,614.6883,695.7781,226.7791,616.832,852.4496,271.2058,610.2564,863.7248,262.9456,476.7759,671.531,189.358,429.4105,688.8552,281.5561,634.2137,701.8088,346.905,640.4838,684.5286,307.1183,615.1978,710.6627,320.5498,647.1286,697.046,251.7334,636.1204,681.4334,406.6233,616.6637,713.0258,420.0116,613.256,750.3001,403.5811,621.7412,790.0734,381.7484,627.6573,677.5214,340.2538,602.7751,826.2079,346.7736,618.6909,810.003,355.046,642.8276,839.3194,361.179,496.3022,689.2396,374.8675,633.785,659.2626,442.6579,471.5569,694.3366,461.8217,445.0035,654.452,391.1036,370.0847,720.429,445.5902,408.5379,712.1207,442.1964,352.4502,681.4266,471.7007,305.6301,667.4104,408.8484,283.1616,642.2411,511.9053,221.2296,586.9419,465.6356,214.8745,682.7589,456.9783,258.581,579.5339,524.1127,174.0574,555.5286,558.2855,96.1944,580.1707,552.2042,115.1923,586.5839,528.1054,65.2425,597.7908,529.4123,98.2027,565.7131,582.997,23.4497,566.4622,544.4066,5.5878,575.8179,538.4122,36.9019,571.1225,565.1187,51.6695,65.8682,-256.722,179.7522,54.7457,-222.9974,179.5555,-71.0265,145.3044,178.223,-83.1531,178.3186,178.1303,64.2322,-171.1969,225.963,-32.5433,110.5981,226.3184,249.3244,208.0428,229.3707,346.7916,-76.0891,229.8203 +75,0.74,727.1401,78.6294,191.5883,637.8198,308.6438,176.0624,862.3022,203.0578,220.3544,824.5764,308.0326,218.4486,695.6119,226.7471,616.7051,708.4141,195.6222,614.6196,695.6119,226.7471,616.7051,852.2589,271.2054,610.2731,863.5592,262.9842,476.8046,671.4441,189.3309,429.3319,688.6835,281.518,634.1423,701.6205,346.8504,640.4564,684.3419,307.0784,615.1602,710.4781,320.5122,647.0861,696.8834,251.6968,636.059,681.1843,406.5402,616.6531,712.7639,419.9992,613.2383,750.0632,403.5681,621.7191,789.8274,381.7664,627.6596,677.3657,340.1893,602.7266,825.9794,346.8005,618.7138,809.7441,355.0794,642.842,839.1349,361.2219,496.33,689.0312,374.816,633.741,658.7212,442.6888,471.5798,693.7565,461.9825,445.1202,654.23,391.3681,370.2265,719.7457,445.6897,408.6242,711.566,442.4656,352.5565,680.6913,472.0147,305.8494,666.6866,409.1366,283.2766,641.5223,512.3113,221.4824,586.2695,465.9672,214.9381,682.0956,457.4286,258.8818,578.9185,524.4858,174.209,555.0242,558.895,96.354,579.6205,552.6943,115.3697,586.1976,528.6987,65.3796,597.3047,529.9543,98.3804,565.2744,583.6066,23.6108,566.0852,545.0881,5.7039,575.4517,539.0358,37.0356,570.6591,565.7144,51.8158,65.8704,-256.7135,179.7503,54.7388,-222.9959,179.5592,-71.0247,145.3032,178.2245,-83.1482,178.3217,178.1313,64.2275,-171.1941,225.9545,-32.5468,110.6007,226.3225,249.3329,208.0382,229.3714,346.7927,-76.0953,229.8199 +76,0.75,727.0406,78.6138,191.5693,637.679,308.6237,176.025,862.189,203.0662,220.3795,824.4483,308.0357,218.4605,695.4565,226.7224,616.6347,708.247,195.5967,614.5662,695.4565,226.7224,616.6347,852.0529,271.1849,610.3067,863.3842,262.9994,476.8201,671.3469,189.3096,429.2475,688.499,281.4742,634.0793,701.4152,346.8013,640.4294,684.201,307.0491,615.0999,710.2973,320.4668,647.0432,696.6987,251.6636,636,680.89,406.465,616.6905,712.5029,419.9555,613.2328,749.8295,403.5613,621.7241,789.5901,381.7664,627.6777,677.204,340.1173,602.6844,825.7844,346.8163,618.7496,809.5043,355.0752,642.8715,838.9027,361.1646,496.3781,688.8129,374.7505,633.7128,658.2061,442.7048,471.6929,693.1929,462.1302,445.231,653.3563,391.443,370.2119,719.0905,445.8603,408.7255,710.4114,442.797,352.747,679.9137,472.2949,306.073,666.0184,409.4092,283.4078,640.8307,512.6349,221.7076,585.6196,466.2718,215.0166,681.343,457.7515,259.1298,578.3227,524.8516,174.3691,554.54,559.3611,96.4856,579.1083,553.1492,115.523,586.262,529.4009,65.5876,596.8303,530.4686,98.5296,564.822,584.2018,23.7462,565.7971,545.6909,5.8137,574.6946,539.5005,37.042,570.2394,566.2678,51.9694,65.8665,-256.7144,179.7589,54.7473,-222.9957,179.5556,-71.012,145.3,178.2205,-83.1477,178.3214,178.1305,64.2285,-171.1931,225.963,-32.5427,110.601,226.3276,249.3351,208.04,229.371,346.7905,-76.0937,229.8202 +77,0.76,726.9302,78.5939,191.5685,637.4893,308.6549,175.9889,862.0681,203.053,220.3945,824.3194,308.0212,218.4818,695.2737,226.6723,616.5757,708.0582,195.5478,614.4905,695.2737,226.6723,616.5757,851.8261,271.1332,610.35,863.2301,262.9791,476.8571,671.2281,189.2971,429.1581,688.2984,281.4295,634.0206,701.2098,346.7321,640.3957,684.0171,306.985,615.0599,710.1085,320.4133,647.0165,696.4937,251.6134,635.9449,680.7399,406.3622,616.6188,712.2657,419.9198,613.247,749.6121,403.5182,621.7281,789.3923,381.7487,627.7086,677.031,340.0425,602.6548,825.5788,346.7999,618.7982,809.2619,355.0356,642.8956,838.7845,361.2194,496.4082,688.6171,374.6827,633.6856,657.7414,442.7438,471.906,692.6206,462.2585,445.3441,653.048,391.6953,370.2863,718.5388,446.0813,408.8337,709.7913,443.1331,352.8905,679.1149,472.5464,306.3004,665.3571,409.7181,283.529,640.1261,513.0012,221.9838,585.0013,466.5372,215.0662,680.7714,458.0302,259.3147,577.8072,525.1143,174.363,553.9254,559.9156,96.8038,578.6233,553.5841,115.6828,585.4737,529.776,65.6206,596.4122,530.9534,98.6996,564.4215,584.7343,23.8656,565.454,546.2646,5.8787,574.3687,540.0506,37.1383,569.8151,566.8051,52.0672,65.8667,-256.716,179.7542,54.7513,-222.997,179.5441,-71.0184,145.2967,178.2216,-83.1426,178.3144,178.1271,64.2335,-171.2014,225.9646,-32.5425,110.6003,226.3197,249.3325,208.0443,229.3717,346.8534,-76.1158,229.8751 +78,0.77,726.7904,78.5883,191.5412,637.3551,308.6451,175.9556,861.9323,203.0322,220.4057,824.1897,308.0024,218.5007,695.0715,226.6192,616.5173,707.854,195.498,614.4314,695.0715,226.6192,616.5173,851.62,271.0604,610.3745,863.0583,262.9312,476.89,671.0677,189.323,429.0604,688.0745,281.2843,633.8958,701.009,346.6575,640.3777,683.8464,306.9343,615.0041,709.9064,320.348,647.0063,696.3039,251.5459,635.8932,680.5359,406.2739,616.6299,712.053,419.8548,613.2654,749.4007,403.465,621.7501,789.1786,381.7052,627.7487,676.8555,339.9743,602.6295,825.3833,346.7521,618.8539,809.0277,354.9666,642.9446,838.5654,361.1102,496.4769,688.4179,374.6003,633.6806,657.2482,442.7423,471.9453,692.0682,462.3839,445.4863,652.5183,391.8032,370.3666,717.9658,446.2364,408.9553,709.2224,443.2277,352.9517,678.3578,472.8,306.5274,664.7393,409.9483,283.6602,639.4599,513.3109,222.2334,584.4236,466.7695,215.1418,679.9406,458.4045,259.6059,577.2128,525.427,174.5917,553.4948,560.2024,96.8769,578.1493,553.9807,115.8343,585.5812,530.4227,65.8218,596.0109,531.4072,98.8517,564.0153,585.231,23.9754,565.1816,546.7955,5.9672,574.0615,540.5526,37.2457,569.7027,567.3531,52.1756,65.8679,-256.7165,179.7557,54.7563,-222.9959,179.5561,-71.0186,145.2964,178.2224,-83.1463,178.319,178.1288,64.2319,-171.1978,225.9608,-32.5333,110.6047,226.3181,249.3334,208.0415,229.3718,346.778,-76.0841,229.8623 +79,0.78,726.6318,78.585,191.513,637.2267,308.651,175.9186,861.7795,203.0049,220.4162,824.0575,307.9854,218.5246,694.8541,226.555,616.4557,707.6431,195.4436,614.369,694.8541,226.555,616.4557,851.3859,270.9722,610.4141,862.8745,262.8662,476.9381,670.8893,189.3378,428.9714,687.8509,281.3178,633.8939,700.8071,346.5909,640.3759,683.6466,306.8752,614.9616,709.7131,320.2517,647.0793,696.0875,251.4719,635.8439,680.3403,406.1781,616.6604,711.8406,419.7787,613.2947,749.2006,403.4035,621.7852,788.9657,381.6389,627.793,676.6879,339.915,602.6232,825.0287,346.6215,618.7828,808.8548,354.9337,643.0813,838.4716,361.123,496.5229,688.2285,374.5173,633.6841,656.8036,442.7598,472.092,691.5515,462.5144,445.6489,651.7893,391.8072,370.4911,717.3812,446.3801,409.0742,708.5996,443.4625,353.0938,677.6072,473.0127,306.7542,664.1299,410.1843,283.7996,638.8096,513.6088,222.4978,583.8345,466.9883,215.2073,679.2946,458.6666,259.8696,576.6663,525.6751,174.6058,553.2574,560.4341,96.7606,577.6846,554.3519,116.0014,585.3054,530.9131,65.95,595.6337,531.8522,99.032,563.6053,585.6935,24.0886,564.9366,547.2725,6.0625,573.8033,541.0455,37.355,569.3221,567.7908,52.2813,65.8651,-256.7192,179.7529,54.7458,-222.9955,179.5637,-71.0184,145.2967,178.2216,-83.1481,178.322,178.1308,64.2313,-171.2007,225.9596,-32.5386,110.594,226.3218,249.3244,208.0391,229.3743,346.8072,-76.0933,229.8781 +80,0.79,726.4655,78.5842,191.5091,637.1029,308.6838,175.8841,861.6378,202.9705,220.4475,823.9149,307.9669,218.5337,694.6247,226.4904,616.404,707.4037,195.3691,614.3084,694.6247,226.4904,616.404,851.1354,270.8608,610.4536,862.6776,262.7943,476.983,670.7046,189.3768,428.8793,687.6207,281.2407,633.85,700.5878,346.4926,640.3735,683.4399,306.8057,614.9325,709.4902,320.1627,647.0613,695.8547,251.3893,635.7901,680.1435,406.0868,616.7045,711.6335,419.709,613.3378,749.0038,403.3199,621.8208,788.7816,381.5406,627.8437,676.4803,339.8287,602.6269,824.811,346.5154,618.8451,808.5679,354.7892,643.0557,838.3164,361.0469,496.6005,688.0212,374.4341,633.6976,656.4384,442.7776,472.3261,691.0947,462.5699,445.7952,651.5151,392.0324,370.5635,716.8354,446.5059,409.1842,708.0333,443.6382,353.226,676.9582,473.2384,306.9799,663.5575,410.3881,283.9508,638.2046,513.8613,222.7534,583.3048,467.1772,215.3031,678.7866,458.8653,260.0385,576.1307,525.9112,174.7615,552.6868,560.9924,97.0902,577.2378,554.6946,116.1967,585.0185,531.3429,66.1085,595.2659,532.2605,99.2225,563.2147,586.1183,24.2082,564.6887,547.7339,6.1899,573.5391,541.483,37.4749,568.9376,568.2122,52.414,65.8705,-256.7183,179.7582,54.7368,-222.9972,179.5576,-71.0152,145.2944,178.2197,-83.1534,178.3185,178.1303,64.228,-171.196,225.9558,-32.5412,110.5953,226.3199,249.3281,208.0403,229.3791,346.8001,-76.0924,229.8729 +81,0.8,726.283,78.5973,191.5012,636.9973,308.7119,175.8654,861.4744,202.9349,220.4795,823.7857,307.9351,218.5675,694.3801,226.4045,616.3482,707.1461,195.2828,614.2485,694.3801,226.4045,616.3482,850.8687,270.7445,610.5214,862.4762,262.6909,477.0386,670.5092,189.4128,428.8101,687.3697,281.1523,633.8195,700.363,346.3964,640.3834,683.2352,306.7273,614.9056,709.253,320.073,647.0629,695.5897,251.3049,635.7492,679.9431,405.9936,616.751,711.4199,419.628,613.3993,748.7893,403.2326,621.8778,788.5673,381.456,627.9122,676.2694,339.752,602.6332,824.553,346.3946,618.9044,808.3252,354.6749,643.1297,838.1021,360.8772,496.6965,687.7967,374.3392,633.7287,656.0439,442.8016,472.4726,690.668,462.633,445.9329,651.0592,392.127,370.6844,716.3395,446.5964,409.3002,707.1957,443.9205,353.4003,676.333,473.4125,307.1923,663.0274,410.5557,284.0895,637.6495,514.069,223.0023,582.81,467.345,215.4013,678.2078,459.0859,260.2774,575.6395,526.1107,174.898,552.4327,561.0724,96.9874,576.799,554.9923,116.375,584.3142,531.6075,66.2025,594.9042,532.6268,99.3966,562.805,586.511,24.3313,564.4423,548.1257,6.3092,573.2734,541.9194,37.6014,568.5378,568.6053,52.5376,65.8572,-256.7164,179.7568,54.7511,-222.9948,179.5573,-71.0184,145.2967,178.2216,-83.144,178.3095,178.1259,64.2319,-171.1989,225.9608,-32.5357,110.601,226.3225,249.3256,208.0379,229.3721,346.7989,-76.0907,229.8694 +82,0.81,726.0587,78.6197,191.5002,636.8932,308.7691,175.8503,861.3163,202.883,220.5147,823.6772,307.9053,218.5957,694.1097,226.3234,616.2998,706.8611,195.1898,614.1934,694.1097,226.3234,616.2998,850.5898,270.6032,610.585,862.2573,262.5945,477.0855,670.3058,189.4423,428.7421,687.0944,281.0603,633.7837,700.1204,346.2994,640.409,683.0098,306.6502,614.8849,709.0046,319.9648,647.08,695.3163,251.1985,635.7148,679.7313,405.8926,616.8076,711.2198,419.5302,613.4652,748.5563,403.1331,621.9309,788.1796,381.2679,627.9,676.0423,339.6755,602.6471,824.3203,346.2557,618.9808,808.1049,354.5877,643.2621,837.9028,360.7779,496.776,687.5674,374.2516,633.7675,655.6921,442.8129,472.6066,690.2749,462.654,446.0738,650.6012,392.1957,370.7929,715.9475,446.6686,409.4074,706.9481,443.9304,353.4906,675.802,473.5693,307.382,662.5593,410.7085,284.2255,637.129,514.2692,223.2264,582.3532,467.4886,215.513,677.6282,459.3344,260.5278,575.1835,526.2871,175.0634,551.9043,561.3586,97.2869,576.3737,555.255,116.5544,584.0297,531.9813,66.3757,594.5436,532.9606,99.6044,562.4163,586.8777,24.4717,564.225,548.5031,6.4365,573.0302,542.2476,37.7457,568.1498,568.9368,52.665,65.8634,-256.7126,179.7491,54.7445,-222.9965,179.5559,-71.0082,145.298,178.2185,-83.1514,178.3148,178.1317,64.2341,-171.1993,225.9555,-32.5432,110.5947,226.3271,249.3259,208.0362,229.3763,346.7923,-76.0899,229.8586 +83,0.82,725.8432,78.6441,191.4886,636.805,308.8358,175.8488,861.1426,202.8185,220.5438,823.5643,307.8734,218.6182,693.8112,226.2183,616.2468,706.5709,195.0965,614.1365,693.8112,226.2183,616.2468,850.2809,270.4503,610.6452,862.0394,262.4872,477.1626,670.0852,189.4607,428.6798,686.8056,280.9576,633.7585,699.8482,346.1775,640.4282,682.719,306.5407,614.8684,708.7141,319.8339,647.1031,695.0189,251.0929,635.687,679.4881,405.785,616.8785,710.9794,419.4296,613.5426,748.3213,403.0157,621.9944,788.0596,381.1674,628.0104,675.7973,339.5827,602.6691,824.0637,346.1072,619.0507,807.8262,354.4232,643.3331,837.7241,360.642,496.8766,687.3282,374.1449,633.8099,655.3489,442.8159,472.7405,689.9307,462.6422,446.1872,650.2417,392.2619,370.9078,715.5502,446.6791,409.5052,706.5421,444.0246,353.6333,675.3002,473.705,307.5711,662.1595,410.848,284.3469,636.6653,514.4674,223.439,581.9645,467.6031,215.6158,677.2047,459.5348,260.7205,574.7423,526.4173,175.2247,551.6121,561.3444,97.2097,575.9468,555.4584,116.7484,583.1819,532.3879,66.463,594.1915,533.2607,99.7822,562.0357,587.1609,24.6305,564.0353,548.8268,6.5772,572.7774,542.5776,37.9045,567.7566,569.1997,52.8241,65.8583,-256.7156,179.7562,54.7483,-222.998,179.5416,-71.0062,145.299,178.2173,-83.1446,178.3188,178.1282,64.2277,-171.2025,225.9599,-32.5392,110.5994,226.3232,249.3295,208.0426,229.379,346.7869,-76.088,229.8593 +84,0.83,725.6019,78.6671,191.4991,636.7188,308.9042,175.8542,860.9932,202.7547,220.5737,823.4613,307.8227,218.6275,693.4926,226.0965,616.1895,706.2383,194.9711,614.0751,693.4926,226.0965,616.1895,850.0048,270.2953,610.7179,861.8239,262.3764,477.2329,669.8586,189.44,428.6148,686.5048,280.8399,633.7291,699.5547,346.0392,640.4474,682.4388,306.4443,614.85,708.4067,319.6981,647.1176,694.6843,250.9556,635.6487,679.2283,405.6623,616.9417,710.7156,419.3022,613.6042,748.0587,402.8754,622.0606,787.7847,381.0128,628.093,675.518,339.4701,602.6949,823.7704,345.9375,619.1419,807.5367,354.2502,643.412,837.5727,360.594,496.9574,687.0471,374.0187,633.8547,655.0494,442.7931,472.8522,689.652,462.589,446.2868,649.6786,392.1679,371.0237,715.1997,446.6093,409.6183,705.9117,444.1931,353.7775,674.9088,473.8288,307.7353,661.8534,410.9775,284.4379,636.2608,514.6185,223.6514,581.6233,467.6619,215.6943,676.9478,459.6526,260.8273,574.3826,526.5082,175.5043,551.1603,561.6017,97.4132,575.5211,555.6049,116.9426,583.9763,532.7187,66.7701,593.8593,533.5173,99.9738,561.5919,587.4015,24.8145,563.7679,549.085,6.7154,572.5134,542.8696,38.036,567.3295,569.4285,52.9575,65.8646,-256.7133,179.7519,54.7359,-222.9969,179.5576,-71.0159,145.2941,178.2199,-83.1393,178.3125,178.1263,64.2311,-171.1987,225.9578,-32.5393,110.5925,226.3279,249.3359,208.0406,229.3823,346.7854,-76.092,229.8478 +85,0.84,725.3572,78.6972,191.4982,636.6317,308.9851,175.8573,860.821,202.6681,220.5924,823.3749,307.7708,218.6472,693.1722,225.9692,616.1315,705.9027,194.8525,614.0045,693.1722,225.9692,616.1315,849.6535,270.1199,610.7972,861.5682,262.2384,477.3105,669.6396,189.4178,428.5524,686.1649,280.6899,633.6909,699.2416,345.8936,640.4745,682.142,306.3198,614.7581,708.0805,319.5394,647.1438,694.3441,250.8185,635.6033,678.8909,405.5453,617.0546,710.4406,419.1595,613.6746,747.7643,402.7285,622.1451,787.4601,380.849,628.1795,675.2194,339.3498,602.7056,823.4653,345.7713,619.235,807.2087,354.0766,643.5015,837.3379,360.4575,497.0591,686.7467,373.8773,633.8917,654.7653,442.7494,472.9576,689.3676,462.5462,446.384,649.5829,392.3178,371.05,714.9453,446.6004,409.7134,705.8491,444.1626,353.8341,674.5867,473.9468,307.8666,661.6552,411.0761,284.5134,635.8684,514.7501,223.8093,581.3645,467.6745,215.7766,676.6835,459.8281,260.9576,573.9972,526.5289,175.6318,550.7136,561.6432,97.5699,575.0701,555.7134,117.1261,583.2219,532.8169,66.858,593.5029,533.728,100.1658,561.1247,587.6152,24.997,563.525,549.3328,6.8704,572.2415,543.1035,38.2045,566.8682,569.6045,53.1288,65.8642,-256.713,179.7504,54.736,-222.9966,179.5594,-71.0152,145.2944,178.2197,-83.1479,178.3189,178.129,64.2268,-171.1982,225.9627,-32.5362,110.593,226.3203,249.3289,208.0373,229.3716,346.7858,-76.0901,229.8492 +86,0.85,725.1151,78.7389,191.5085,636.5466,309.0713,175.8849,860.6516,202.5995,220.6075,823.2803,307.7239,218.6445,692.8046,225.8159,616.0692,705.5374,194.6953,613.9359,692.8046,225.8159,616.0692,849.3253,269.945,610.9017,861.3029,262.1129,477.3993,669.4067,189.4016,428.4787,685.7939,280.5332,633.6608,698.896,345.7356,640.4996,681.8057,306.1733,614.7978,707.7471,319.3728,647.1556,693.9767,250.6468,635.5607,678.634,405.3792,617.0695,710.1312,419.0136,613.767,747.4374,402.5789,622.2333,787.1481,380.6604,628.2704,674.9069,339.2053,602.7217,823.1218,345.5796,619.3248,806.8647,353.8773,643.5915,837.0081,360.2506,497.1902,686.4075,373.7248,633.9418,654.5056,442.7003,473.0541,689.098,462.4871,446.4786,649.3807,392.3154,371.0842,714.7274,446.5755,409.7928,705.3966,444.3428,353.9803,674.3233,474.0641,307.9905,661.5851,411.2055,284.5628,635.5343,514.8571,223.9607,581.1917,467.6298,215.8353,676.5322,460.0187,261.0649,573.6103,526.4974,175.7931,550.0936,561.6511,97.8871,574.5953,555.7764,117.341,583.0046,533.0231,67.0177,593.1698,533.9332,100.357,560.5665,587.7898,25.225,563.2854,549.5743,7.0175,572.0197,543.3014,38.3854,566.3664,569.7524,53.2889,65.8622,-256.7169,179.7541,54.736,-222.9968,179.5585,-71.0101,145.3009,178.2194,-83.1477,178.321,178.1294,64.2251,-171.1954,225.9604,-32.5398,110.5912,226.325,249.3281,208.0399,229.3762,346.7809,-76.09,229.8428 +87,0.86,724.8659,78.7853,191.5121,636.4573,309.1699,175.8972,860.4822,202.5374,220.628,823.1802,307.6699,218.6606,692.4378,225.6601,616.0002,705.1656,194.5331,613.8713,692.4378,225.6601,616.0002,848.954,269.7539,610.9847,861.0508,261.9852,477.4826,669.148,189.3504,428.3896,685.4232,280.3561,633.6277,698.5195,345.5522,640.5287,681.4659,306.0284,614.7742,707.3663,319.183,647.1903,693.5975,250.4823,635.5158,678.2879,405.21,617.1226,709.7786,418.8526,613.8624,747.0835,402.3945,622.3347,786.7679,380.4539,628.3696,674.5572,339.0354,602.7206,822.7563,345.3873,619.4449,806.483,353.6841,643.6961,836.7529,360.1153,497.3238,686.0491,373.5412,633.9683,654.2236,442.6072,473.1094,688.8854,462.4161,446.5808,649.2743,392.3041,371.0957,714.5656,446.566,409.9036,705.4157,444.496,354.1208,674.17,474.204,308.0901,661.6453,411.3206,284.5725,635.2146,514.9732,224.1017,581.1127,467.5211,215.8835,676.3558,460.2942,261.2005,573.1875,526.3958,175.8077,549.6525,561.4378,97.9296,574.0745,555.7765,117.5543,583.1786,533.4285,67.129,592.8177,534.1215,100.5429,559.9738,587.9074,25.4628,563.1138,549.7697,7.1988,571.7665,543.5101,38.5707,565.8213,569.8649,53.4782,65.8633,-256.7173,179.7554,54.7533,-222.9968,179.5537,-71.0201,145.2979,178.2202,-83.1467,178.3201,178.1278,64.2262,-171.1967,225.9645,-32.5452,110.5943,226.3233,249.3284,208.0423,229.3763,346.7867,-76.0868,229.8341 +88,0.87,724.6165,78.8218,191.5132,636.3654,309.2704,175.9201,860.3033,202.4663,220.632,823.0848,307.6153,218.6899,692.0422,225.473,615.934,704.7617,194.3504,613.7906,692.0422,225.473,615.934,848.5446,269.5275,611.0854,860.7669,261.855,477.5862,668.9114,189.2938,428.2805,685.0168,280.1643,633.5923,698.1089,345.3528,640.5608,681.0936,305.8581,614.7394,706.9464,318.9709,647.2246,693.186,250.2781,635.4769,677.9043,405.0214,617.1581,709.4002,418.6639,613.9597,746.7077,402.197,622.4403,786.3816,380.2502,628.4845,674.1962,338.8641,602.7159,822.3582,345.1809,619.5824,806.0627,353.4518,643.7976,836.4564,359.9719,497.4567,685.6332,373.3371,634.0095,653.9698,442.5124,473.1668,688.665,462.3339,446.6826,649.0682,392.1869,371.1201,714.4992,446.6358,410.0309,705.5741,444.5488,354.1685,674.1002,474.3785,308.1832,661.8618,411.4735,284.5726,634.967,515.0848,224.24,581.148,467.3435,215.9067,676.4276,460.5663,261.2556,572.824,526.2153,175.9683,548.9424,561.3904,98.1674,573.483,555.7249,117.7739,582.5778,533.5651,67.3492,592.4918,534.3293,100.7527,559.2316,588.0134,25.7259,562.9035,549.9434,7.3787,571.5244,543.6999,38.7617,565.1614,569.9265,53.6976,65.8556,-256.7158,179.75,54.7426,-222.998,179.5537,-71.0271,145.3041,178.2231,-83.1544,178.3196,178.129,64.2274,-171.1911,225.9622,-32.5367,110.6006,226.3191,249.334,208.0428,229.3768,346.7859,-76.0999,229.8237 +89,0.88,724.3742,78.8497,191.5195,636.2679,309.3555,175.9247,860.144,202.4084,220.6508,822.9648,307.5659,218.6954,691.6198,225.2757,615.8608,704.3393,194.1442,613.7061,691.6198,225.2757,615.8608,848.119,269.3077,611.2054,860.475,261.7221,477.7208,668.6869,189.2168,428.1512,684.5693,279.9456,633.5538,697.6703,345.1337,640.5754,680.6765,305.6496,614.7037,706.5287,318.7433,647.2155,692.7457,250.068,635.4252,677.4108,404.8294,617.281,708.9611,418.4568,614.0624,746.2617,401.9781,622.5532,785.9508,379.9953,628.5968,673.782,338.6462,602.7058,821.926,344.9571,619.7141,805.5902,353.2108,643.918,836.1242,359.8315,497.6093,685.1962,373.1226,634.0403,653.7426,442.3967,473.1973,688.4611,462.244,446.7757,649.3705,392.3272,371.0345,714.4462,446.6745,410.1443,705.5892,444.7395,354.2742,674.1586,474.5785,308.2486,662.272,411.6358,284.5368,634.7291,515.2013,224.3501,581.2977,467.1008,215.9102,676.6151,460.8363,261.2639,572.421,525.9355,176.1174,548.188,560.819,98.31,572.751,555.6006,118.0566,582.2977,533.7331,67.5536,592.1084,534.5197,100.9978,558.3077,588.0874,26.0733,562.7136,550.1241,7.6246,571.2487,543.8833,39.0086,564.377,569.9311,53.9373,65.8559,-256.7143,179.7546,54.7465,-222.9953,179.5542,-71.0265,145.3044,178.223,-83.1549,178.3189,178.1286,64.2204,-171.1924,225.9588,-32.5446,110.5902,226.3207,249.3369,208.0412,229.3709,346.782,-76.0907,229.83 +90,0.89,724.1351,78.8826,191.5169,636.1644,309.4332,175.9169,859.9531,202.3618,220.6644,822.8588,307.5248,218.7091,691.1528,225.0417,615.7815,703.8967,193.9223,613.5961,691.1528,225.0417,615.7815,847.6396,269.0707,611.3307,860.1692,261.5869,477.8323,668.4471,189.1376,428.0127,684.0869,279.7048,633.5048,697.1548,344.8837,640.5919,680.1935,305.4309,614.6591,706.0562,318.4856,647.3014,692.2585,249.8238,635.3254,676.9122,404.6063,617.3293,708.4916,418.2347,614.1559,745.7839,401.7343,622.6605,785.4421,379.7338,628.7199,673.3366,338.4216,602.6764,821.4441,344.7094,619.8497,805.0253,352.8959,643.9529,835.8017,359.687,497.7847,684.6915,372.8745,634.0709,653.4412,442.2275,473.0605,688.2857,462.1609,446.8624,649.5904,392.3619,370.9488,714.4258,446.7608,410.2741,705.7286,444.9975,354.3726,674.3569,474.8255,308.2834,662.874,411.8336,284.4348,634.5478,515.3179,224.4158,581.6276,466.7774,215.8962,676.9987,461.183,261.2248,571.9875,525.5475,176.343,547.1632,560.25,98.5665,571.7516,555.3605,118.3398,581.9633,534.1172,67.8281,591.6635,534.7396,101.2891,557.2346,588.1022,26.5037,562.5244,550.2994,7.9355,570.9594,544.0618,39.3456,563.3908,569.8929,54.2966,65.8621,-256.7179,179.7505,54.7448,-222.9945,179.5628,-71.0201,145.2979,178.2202,-83.1543,178.3192,178.1283,64.222,-171.1947,225.9596,-32.5403,110.5877,226.3208,249.328,208.0456,229.3776,346.786,-76.0933,229.83 +91,0.9,723.8835,78.9056,191.5154,636.0628,309.5088,175.9038,859.7774,202.3078,220.6859,822.7287,307.4742,218.7214,690.6764,224.7899,615.6899,703.4034,193.6684,613.5146,690.6764,224.7899,615.6899,847.164,268.8228,611.4626,859.504,261.2847,477.7364,668.179,189.0327,427.858,683.5519,279.4354,633.4564,696.592,344.6159,640.6125,679.7258,305.1937,614.5502,705.4967,318.2192,647.3139,691.7338,249.5462,635.2626,676.4396,404.3553,617.2953,707.951,417.9816,614.2504,745.2358,401.4594,622.7686,784.9037,379.444,628.8464,672.8196,338.1743,602.6476,820.897,344.4565,620.0102,804.5302,352.6403,644.1471,835.426,359.5156,497.9555,684.1216,372.6097,634.0894,653.2685,442.0901,473.0252,688.1711,462.08,446.9461,649.968,392.4547,370.8415,714.5309,446.8847,410.4127,706.0388,445.2974,354.4928,674.7862,475.1166,308.2801,663.7277,412.0803,284.3059,634.4495,515.4505,224.4598,582.1292,466.4008,215.8994,677.5543,461.5627,261.1035,571.4791,525.0467,176.6044,545.7953,559.6666,98.9485,570.5627,554.9877,118.7846,581.4039,534.3669,68.2049,590.9844,534.9552,101.6833,555.8634,588.0992,27.1003,562.342,550.5211,8.3678,570.566,544.2275,39.8113,562.1874,569.7922,54.7948,65.8592,-256.7159,179.755,54.7475,-222.9913,179.5549,-71.0265,145.3044,178.223,-83.1564,178.3181,178.1331,64.2203,-171.1919,225.9688,-32.5387,110.5985,226.3241,249.3265,208.0421,229.3751,346.7935,-76.0912,229.8202 +92,0.91,723.6514,78.9321,191.5034,635.9476,309.5773,175.8796,859.5917,202.259,220.7132,822.6021,307.4319,218.7467,690.1493,224.507,615.5966,702.8943,193.3954,613.3861,690.1493,224.507,615.5966,846.6461,268.5538,611.6188,859.1799,261.1301,477.861,667.9154,188.9326,427.6922,682.9672,279.149,633.4026,695.9716,344.3112,640.6088,679.1611,304.924,614.5482,704.91,317.9129,647.3004,691.1808,249.2637,635.1813,675.8099,404.102,617.3318,707.3444,417.7112,614.3528,744.6191,401.1574,622.9016,784.2573,379.1314,628.9857,672.249,337.897,602.603,820.2923,344.1825,620.1829,803.9018,352.3208,644.2861,835.0445,359.3635,498.1624,683.4764,372.3057,634.1108,653.1483,441.9425,472.9541,688.1277,462.0685,447.0442,650.4553,392.5805,370.692,714.7046,446.9957,410.5555,706.4092,445.6985,354.6332,675.4838,475.4557,308.2173,664.8716,412.3581,284.1066,634.4456,515.5846,224.4505,582.8631,465.9696,215.9379,678.4067,462.0252,260.8951,570.9045,524.381,176.9749,544.0533,558.3918,99.3188,569.0054,554.4795,119.3811,580.4951,534.6065,68.6848,590.0975,535.1874,102.1983,554.1428,588.09,27.9207,562.1697,550.7803,8.9958,570.1369,544.5067,40.526,560.61,569.6257,55.5102,65.8687,-256.7148,179.7555,54.7476,-222.9914,179.5557,-71.0265,145.3044,178.223,-83.1493,178.322,178.1293,64.2211,-171.1919,225.9685,-32.5445,110.6031,226.3257,249.3245,208.0495,229.3756,346.7876,-76.0916,229.8188 +93,0.92,723.4233,78.9537,191.5159,635.8636,309.6195,175.8459,859.4224,202.2008,220.7414,822.4816,307.4034,218.7642,689.5886,224.2027,615.4978,702.3353,193.0919,613.2809,689.5886,224.2027,615.4978,846.0764,268.2668,611.7772,858.9162,261.0227,478.0612,667.6446,188.8485,427.5319,682.3267,278.8166,633.3246,695.2646,343.9898,640.6225,678.5586,304.6282,614.4378,704.2371,317.589,647.299,690.5626,248.937,635.1002,675.1016,403.8165,617.3533,706.6619,417.408,614.4558,743.9166,400.8377,623.0391,783.5736,378.7923,629.1432,671.6086,337.5983,602.5433,819.6176,343.9081,620.3895,803.1954,351.9951,644.4469,834.6447,359.2847,498.3725,682.7527,371.9763,634.1161,653.1171,441.8133,472.8505,688.1685,462.0407,447.1247,651.1907,392.7469,370.5047,715.038,447.1725,410.7176,707.1485,446.1092,354.6918,676.5056,475.8428,308.1071,666.3201,412.7182,283.8299,634.5602,515.7352,224.4016,583.8002,465.478,215.9883,679.4414,462.5641,260.6017,570.251,523.5927,177.4683,541.7899,556.9601,99.9843,567.0542,553.7916,120.1261,579.3078,534.8956,69.3621,588.9525,535.4328,102.8819,551.9611,587.9543,28.969,561.9896,551.0467,9.902,569.3873,544.7548,41.4395,558.6053,569.3618,56.4528,65.8727,-256.7148,179.7595,54.7476,-222.9911,179.5564,-71.0055,145.2993,178.2172,-83.1524,178.318,178.1268,64.2247,-171.1899,225.9684,-32.5388,110.5956,226.3225,249.328,208.0376,229.3726,346.7847,-76.0879,229.8264 diff --git a/tests/data/markers.xlsx b/tests/data/markers.xlsx index 53befad..b3e53ac 100644 Binary files a/tests/data/markers.xlsx and b/tests/data/markers.xlsx differ diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py new file mode 100644 index 0000000..804874d --- /dev/null +++ b/tests/test_docstrings.py @@ -0,0 +1,79 @@ +import inspect + +import black +import pytest + +import pyomeca +from tests.utils import ( + DocStringError, + do_we_generate_doc_files, + extract_code_blocks_from_md, + function_has_return, + generate_api_json, + get_available_methods, +) + +generate_doc_files = do_we_generate_doc_files() +methods = get_available_methods(pyomeca) +if generate_doc_files: + generate_api_json(pyomeca) + + +@pytest.mark.parametrize("method", methods) +def test_has_docstring(method): + if not method.__doc__: + raise DocStringError(f"Missing docstring in `{method}`") + + +@pytest.mark.parametrize("method", methods) +def test_docstring_has_example(method): + if "```python" not in method.__doc__: + raise DocStringError(f"Missing example in `{method}` docstring") + + +@pytest.mark.parametrize("method", methods) +def test_docstring_example(method): + plt_show_replacer = ( + f'plt.savefig("docs/images/api/{method.__name__}.svg", bbox_inches="tight")\nplt.figure()' + if generate_doc_files + else "" + ) + code_block = extract_code_blocks_from_md(method.__doc__).replace( + "plt.show()", plt_show_replacer, + ) + exec(code_block, {}, {}) + + +@pytest.mark.parametrize("method", methods) +def test_docstring_lint_code_blocks(method): + code_blocks = extract_code_blocks_from_md(method.__doc__) + if code_blocks: + code_blocks = f"{code_blocks}\n" + assert code_blocks == black.format_str(code_blocks, mode=black.FileMode()) + + +@pytest.mark.parametrize("method", methods) +def test_docstring_return(method): + if function_has_return(method): + if "Returns:" not in method.__doc__: + raise DocStringError(f"Missing returns in `{method}` docstring") + if "return" not in inspect.getfullargspec(method).annotations: + raise DocStringError( + f"Type annotation missing for the `return` type in {method} docstring" + ) + + +@pytest.mark.parametrize("method", methods) +def test_docstring_parameters(method): + funct_with_ignored_args = "cls", "self" + argspec = inspect.getfullargspec(method) + args = [a for a in argspec.args if a not in funct_with_ignored_args] + if args and "Arguments:" not in method.__doc__: + raise DocStringError(f"`Arguments` block missing in `{method}` docstring") + for arg in args: + if arg in funct_with_ignored_args: + continue + if arg not in method.__doc__: + raise DocStringError(f"{arg} not described in {method} docstring") + if arg not in argspec.annotations: + raise DocStringError(f"{arg} not type annotated in {method}") diff --git a/tests/test_documentation_code_blocks.py b/tests/test_documentation_code_blocks.py new file mode 100644 index 0000000..2c5d21f --- /dev/null +++ b/tests/test_documentation_code_blocks.py @@ -0,0 +1,27 @@ +from pathlib import Path + +import black +import pytest + +from tests.utils import extract_code_blocks_from_md + +docs_path = Path("./docs") +doc_files = [f"{file}" for file in docs_path.glob("*.md")] + +doc_files_string = [] +for file in doc_files: + with open(f"{file}", "r", encoding="utf8") as f: + doc_files_string.append(f.read().replace("../tests/data", "tests/data")) + + +@pytest.mark.parametrize("doc_file_string", doc_files_string, ids=doc_files) +def test_code_blocks(doc_file_string): + exec(extract_code_blocks_from_md(doc_file_string), {}, {}) + + +@pytest.mark.parametrize("doc_file_string", doc_files_string, ids=doc_files) +def test_lint_code_blocks(doc_file_string): + code_blocks = extract_code_blocks_from_md(doc_file_string) + if code_blocks: + code_blocks = f"{code_blocks}\n" + assert code_blocks == black.format_str(code_blocks, mode=black.FileMode()) diff --git a/tests/test_ezc3d.py b/tests/test_ezc3d.py new file mode 100644 index 0000000..fbb8376 --- /dev/null +++ b/tests/test_ezc3d.py @@ -0,0 +1,11 @@ +import ezc3d +import xarray as xr + +from ._constants import EXPECTED_VALUES, MARKERS_ANALOGS_C3D +from .utils import is_expected_array + + +def test_ezc3d(): + c3d = ezc3d.c3d(f"{MARKERS_ANALOGS_C3D}") + is_expected_array(xr.DataArray(c3d["data"]["points"]), **EXPECTED_VALUES[65]) + is_expected_array(xr.DataArray(c3d["data"]["analogs"]), **EXPECTED_VALUES[66]) diff --git a/tests/test_fileio.py b/tests/test_fileio.py deleted file mode 100644 index 494f79f..0000000 --- a/tests/test_fileio.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -Test for file IO -""" -from pathlib import Path - -import numpy as np -import pytest - -from pyomeca import Analogs3d, Markers3d - -# Path -PROJECT_FOLDER = Path(__file__).parent / ".." -DATA_FOLDER = PROJECT_FOLDER / "tests" / "data" - -MARKERS_CSV = DATA_FOLDER / "markers.csv" -MARKERS_XLSX = DATA_FOLDER / "markers.xlsx" -MARKERS_ANALOGS_C3D = DATA_FOLDER / "markers_analogs.c3d" -ANALOGS_CSV = DATA_FOLDER / "analogs.csv" - - -def compare_csv(filename, kind): - """Assert analogs's to_csv method.""" - idx = [0, 1, 2, 3] - out = Path(".") / "temp.csv" - - if kind == "markers": - header = 2 - arr_ref, arr = Markers3d(), Markers3d() - else: - header = 3 - arr_ref, arr = Analogs3d(), Analogs3d() - # read a csv - arr_ref = arr_ref.from_csv( - filename, first_row=5, first_column=2, header=header, prefix=":", idx=idx - ) - # write a csv - arr_ref.to_csv(out, header=False) - # read the generated csv - arr = arr.from_csv(out, first_row=0, first_column=0, header=0, prefix=":", idx=idx) - - out.unlink() - - a = arr_ref[:, :, 1:100] - b = arr[:, :, 1:100] - - np.testing.assert_equal(a.shape, b.shape) - np.testing.assert_almost_equal(a, b, decimal=1) - - -# --- Test markers data -idx = ( - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [[0, 1, 2], [0, 4, 2]], - [[0], [1], [2]], - None, -) -names = (None, None, None, ["CLAV_post", "PSISl", "STERr", "CLAV_post"]) -expected_shape = ((4, 11, 580), (4, 3, 580), (4, 1, 580), (4, 4, 580)) -expected_values = ( - np.array([3.18461e02, -1.69003e02, 1.05422e03, 1.00000e00]).reshape(-1, 1), - np.array([3.18461e02, -1.69003e02, 1.05422e03, 1.00000e00]).reshape(-1, 1), - np.array([2.62055670e02, -2.65073300e01, 1.04641333e03, 1.00000000e00]).reshape( - -1, 1 - ), - np.array([791.96, 295.588, 682.808, 1.0]).reshape(-1, 1), -) -markers_param = [ - (idx[i], names[i], expected_shape[i], expected_values[i]) for i in range(len(idx)) -] - - -@pytest.mark.parametrize("idx, names, expected_shape, expected_values", markers_param) -@pytest.mark.parametrize("extension", ["c3d", "csv", "xlsx"]) -def test_markers(idx, names, expected_shape, expected_values, extension): - """Assert markers shape.""" - if extension == "csv": - arr = Markers3d.from_csv( - MARKERS_CSV, - first_row=5, - first_column=2, - header=2, - prefix=":", - idx=idx, - names=names, - ) - elif extension == "xlsx": - arr = Markers3d.from_excel( - MARKERS_XLSX, - sheet_name=0, - first_row=5, - first_column=2, - header=2, - prefix=":", - idx=idx, - names=names, - ) - elif extension == "c3d": - arr = Markers3d.from_c3d(MARKERS_ANALOGS_C3D, prefix=":", idx=idx, names=names) - - else: - raise ValueError( - f'extension should be "csv", "c3d" or "xlsx". You provided {extension}' - ) - # test shape - np.testing.assert_equal(arr.shape, expected_shape) - # test values - d = arr[:, 0, int(arr.shape[2] / 2)] - np.testing.assert_almost_equal(d, expected_values, decimal=2) - - -def test_markers_to_csv(): - """Assert analogs's to_csv method.""" - compare_csv(filename=MARKERS_CSV, kind="analogs") - - -# --- Test analogs data -names = (None, None, None, ["EMG1", "EMG11", "EMG5", "EMG13"]) -expected_shape = ((1, 11, 11600), (1, 3, 11600), (1, 1, 11600), (1, 4, 11600)) -expected_values = ( - np.array([[-0.01396]]), - np.array([[-0.01396]]), - np.array([[-0.10447]]), - np.array([[-0.00039]]), -) -analogs_param = [ - (idx[i], names[i], expected_shape[i], expected_values[i]) for i in range(len(idx)) -] - - -@pytest.mark.parametrize("idx, names, expected_shape, expected_values", analogs_param) -@pytest.mark.parametrize("extension", ["c3d", "csv"]) -def test_analogs(idx, names, expected_shape, expected_values, extension): - """Assert markers shape.""" - if extension == "csv": - arr = Analogs3d.from_csv( - ANALOGS_CSV, - first_row=5, - first_column=2, - header=3, - prefix=":", - idx=idx, - names=names, - ) - elif extension == "c3d": - arr = Analogs3d.from_c3d(MARKERS_ANALOGS_C3D, prefix=".", idx=idx, names=names) - else: - raise ValueError(f'extension should be "csv", "c3d". You provided {extension}') - # test shape - np.testing.assert_equal(arr.shape, expected_shape) - # test values - d = arr[:, 0, int(arr.shape[2] / 2)] - np.testing.assert_almost_equal(d, expected_values, decimal=2) - - -def test_analogs_to_csv(): - """Assert analogs's to_csv method.""" - compare_csv(filename=ANALOGS_CSV, kind="analogs") - - -def test_slicing_data(): - c = Analogs3d.from_c3d(MARKERS_ANALOGS_C3D) - n_frames = c.shape[2] - analog1 = "Delt_ant.EMG1" - analog2 = "Delt_med.EMG2" - analog1_idx = c.get_index(analog1)[0] - analog2_idx = c.get_index(analog2)[0] - - # Accessing all values - a = c.copy() - - # Normal slicing one value - c[:, 0, :] = 1.0 - np.testing.assert_almost_equal(c[:, 0, :], np.ones((1, 1, n_frames)), decimal=2) - - # Normal slicing more than one values - c[:, 0:2, :] = 2.0 - np.testing.assert_almost_equal( - c[:, 0:2, :], np.ones((1, 2, n_frames)) * 2, decimal=2 - ) - - # Normal slicing using list - c[:, [0, 1], :] = 3.0 - np.testing.assert_almost_equal( - c[:, 0:2, :], np.ones((1, 2, n_frames)) * 3, decimal=2 - ) - - # Normal slicing using tuple - c[:, (0, 1), :] = 4.0 - np.testing.assert_almost_equal( - c[:, 0:2, :], np.ones((1, 2, n_frames)) * 4, decimal=2 - ) - - # Normal slicing one value part of the time - c[:, 0:2, :] = 1.0 # Reset - c[:, 0, -10:] = 2.0 - np.testing.assert_almost_equal( - c[:, 0, :], - np.concatenate( - (np.ones((1, 1, n_frames - 10)) * 1, np.ones((1, 1, 10)) * 2), axis=2 - ), - decimal=2, - ) - - # Normal slicing more than one values - c[:, 0:2, -10:] = 3.0 - np.testing.assert_almost_equal( - c[:, 0:2, :], - np.concatenate( - (np.ones((1, 2, n_frames - 10)) * 1, np.ones((1, 2, 10)) * 3), axis=2 - ), - decimal=2, - ) - - # Normal slicing using list - c[:, [0, 1], -10:] = 4.0 - np.testing.assert_almost_equal( - c[:, 0:2, :], - np.concatenate( - (np.ones((1, 2, n_frames - 10)) * 1, np.ones((1, 2, 10)) * 4), axis=2 - ), - decimal=2, - ) - - # Normal slicing using tuple - c[:, (0, 1), -10:] = 4.0 - np.testing.assert_almost_equal( - c[:, 0:2, :], - np.concatenate( - (np.ones((1, 2, n_frames - 10)) * 1, np.ones((1, 2, 10)) * 4), axis=2 - ), - decimal=2, - ) - - # Slicing the whole data via its name - c[analog1] = 1.0 - np.testing.assert_almost_equal( - c[:, analog1_idx, :], np.ones((1, 1, n_frames)), decimal=2 - ) - - # Slicing the whole data via their names (list) - c[[analog1, analog2]] = 2.0 - np.testing.assert_almost_equal( - c[:, [analog1_idx, analog2_idx], :], np.ones((1, 2, n_frames)) * 2, decimal=2 - ) - - # Slicing the whole data via their names (tuple) - c[(analog1, analog2)] = 3.0 - np.testing.assert_almost_equal( - c[:, [analog1_idx, analog2_idx], :], np.ones((1, 2, n_frames)) * 3, decimal=2 - ) - - # Slicing part of the data - c[:, analog1, -10:] = 4.0 - np.testing.assert_almost_equal( - c[:, analog1_idx, :], - np.concatenate( - (np.ones((1, 1, n_frames - 10)) * 3, np.ones((1, 1, 10)) * 4), axis=2 - ), - decimal=2, - ) - - # Slicing part of the data (list) - c[:, 0, :] = 1.0 # Reset - c[:, analog1, :] = 1.0 # Reset - c[:, analog2, :] = 1.0 # Reset - c[:, [0, analog1, analog2], :10] = 5.0 - c[:, [0, analog1, analog2], -10:] = 5.0 - np.testing.assert_almost_equal( - c[:, [0, analog1_idx, analog2_idx], :], - np.concatenate( - ( - np.ones((1, 3, 10)) * 5, - np.ones((1, 3, n_frames - 20)), - np.ones((1, 3, 10)) * 5, - ), - axis=2, - ), - decimal=2, - ) - - # Slicing part of the data (tuple) - c[:, (0, analog1, analog2), :10] = 6.0 - c[:, (0, analog1, analog2), -10:] = 6.0 - np.testing.assert_almost_equal( - c[:, [0, analog1_idx, analog2_idx], :], - np.concatenate( - ( - np.ones((1, 3, 10)) * 6, - np.ones((1, 3, n_frames - 20)), - np.ones((1, 3, 10)) * 6, - ), - axis=2, - ), - decimal=2, - ) \ No newline at end of file diff --git a/tests/test_fileio_read_csv_c3d.py b/tests/test_fileio_read_csv_c3d.py new file mode 100644 index 0000000..10c36ee --- /dev/null +++ b/tests/test_fileio_read_csv_c3d.py @@ -0,0 +1,190 @@ +import numpy as np +import pytest + +from pyomeca import Analogs, Markers +from tests._constants import ( + ANALOGS_CSV, + ANALOGS_XLSX, + EXPECTED_VALUES, + MARKERS_ANALOGS_C3D, + MARKERS_CSV, + MARKERS_CSV_WITHOUT_HEADER, + MARKERS_XLSX, +) +from tests.utils import is_expected_array + +_extensions = ["c3d", "csv"] +_analogs_cases = [ + {"usecols": None, **EXPECTED_VALUES[10]}, + {"usecols": ["EMG1", "EMG10", "EMG11", "EMG12"], **EXPECTED_VALUES[11]}, + {"usecols": [1, 3, 5, 7], **EXPECTED_VALUES[12]}, + {"usecols": ["EMG1"], **EXPECTED_VALUES[13]}, + {"usecols": [2], **EXPECTED_VALUES[14]}, +] +analogs_csv_kwargs = dict(filename=ANALOGS_CSV, header=3, first_row=5, first_column=2) +markers_csv_kwargs = dict( + filename=MARKERS_CSV, header=2, first_row=5, first_column=2, prefix_delimiter=":" +) + +_markers_cases = [ + {"usecols": None, **EXPECTED_VALUES[15]}, + {"usecols": ["CLAV_post", "PSISl", "STERr", "CLAV_post"], **EXPECTED_VALUES[16]}, + {"usecols": [1, 3, 5, 7], **EXPECTED_VALUES[17]}, + {"usecols": ["CLAV_post"], **EXPECTED_VALUES[18]}, + {"usecols": [2], **EXPECTED_VALUES[19]}, +] + + +@pytest.mark.parametrize( + "usecols, shape_val, first_last_val, mean_val, median_val, sum_val, nans_val", + [(d.values()) for d in _analogs_cases], +) +@pytest.mark.parametrize("extension", _extensions) +def test_read_analogs( + usecols, + shape_val, + first_last_val, + mean_val, + median_val, + sum_val, + nans_val, + extension, +): + if extension == "csv": + data = Analogs.from_csv(**analogs_csv_kwargs, usecols=usecols) + decimal = 0 + elif extension == "c3d": + data = Analogs.from_c3d( + MARKERS_ANALOGS_C3D, prefix_delimiter=".", usecols=usecols + ) + decimal = 4 + else: + raise ValueError("wrong extension provided") + + if usecols and isinstance(usecols[0], str): + np.testing.assert_array_equal(x=data.channel, y=usecols) + + is_expected_array( + data, + shape_val, + first_last_val, + mean_val, + median_val, + sum_val, + nans_val, + decimal=decimal, + ) + + +@pytest.mark.parametrize("usecols", [[20.0]]) +@pytest.mark.parametrize("extension", _extensions) +def test_read_catch_error( + usecols, extension, +): + with pytest.raises(IndexError): + Markers.from_csv(MARKERS_CSV) + + if extension == "csv": + with pytest.raises(ValueError): + Analogs.from_csv(**analogs_csv_kwargs, usecols=usecols) + with pytest.raises(ValueError): + Markers.from_csv(**markers_csv_kwargs, usecols=usecols) + elif extension == "c3d": + with pytest.raises(ValueError): + Analogs.from_c3d(MARKERS_ANALOGS_C3D, usecols=usecols) + + reader = getattr(Markers, f"from_{extension}") + with pytest.raises(ValueError): + reader(MARKERS_ANALOGS_C3D, usecols=usecols) + + +def test_csv_trailing_columns(): + last_column_to_remove = 5 + ref = Analogs.from_csv(**analogs_csv_kwargs).channel[:-last_column_to_remove] + without_last_columns = Analogs.from_csv( + **analogs_csv_kwargs, trailing_columns=last_column_to_remove + ).channel + np.testing.assert_array_equal(x=ref, y=without_last_columns) + + +def test_csv_edge_cases(): + time_with_rate = Analogs.from_csv(**analogs_csv_kwargs, attrs={"rate": 2000}).time + assert time_with_rate[-1] == 5.7995 + + time_column_with_id = Analogs.from_csv(**analogs_csv_kwargs, time_column="Frame") + + time_column_with_name = Analogs.from_csv(**analogs_csv_kwargs, time_column=0) + + np.testing.assert_array_equal(time_column_with_id.time, time_column_with_name.time) + np.testing.assert_array_equal(time_column_with_id, time_column_with_name) + + with pytest.raises(ValueError): + Analogs.from_csv(**analogs_csv_kwargs, time_column=[20.0]) + + assert Analogs.from_csv( + **analogs_csv_kwargs, prefix_delimiter="G", suffix_delimiter="M" + ).shape == (38, 11600) + + +def test_csv_without_header(): + is_expected_array( + Analogs.from_csv(ANALOGS_CSV, first_row=5, first_column=2), + **EXPECTED_VALUES[59], + ) + + is_expected_array( + Markers.from_csv(MARKERS_CSV_WITHOUT_HEADER, first_column=2), + **EXPECTED_VALUES[58], + ) + + +@pytest.mark.parametrize( + "usecols, shape_val, first_last_val, mean_val, median_val, sum_val, nans_val", + [(d.values()) for d in _markers_cases], +) +@pytest.mark.parametrize("extension", _extensions) +def test_read_markers( + usecols, + shape_val, + first_last_val, + mean_val, + median_val, + sum_val, + nans_val, + extension, +): + if extension == "csv": + data = Markers.from_csv(**markers_csv_kwargs, usecols=usecols) + decimal = 0 + elif extension == "c3d": + data = Markers.from_c3d( + MARKERS_ANALOGS_C3D, prefix_delimiter=":", usecols=usecols + ) + decimal = 4 + else: + raise ValueError("wrong extension provided") + + if usecols and isinstance(usecols[0], str): + np.testing.assert_array_equal(x=data.channel, y=usecols) + + is_expected_array( + data, + shape_val, + first_last_val, + mean_val, + median_val, + sum_val, + nans_val, + decimal=decimal, + ) + + +def test_read_xlsx(): + is_expected_array( + Markers.from_excel(**{**markers_csv_kwargs, **dict(filename=MARKERS_XLSX)}), + **EXPECTED_VALUES[60], + ) + is_expected_array( + Analogs.from_excel(**{**analogs_csv_kwargs, **dict(filename=ANALOGS_XLSX)}), + **EXPECTED_VALUES[61], + ) diff --git a/tests/test_fileio_read_other_formats.py b/tests/test_fileio_read_other_formats.py new file mode 100644 index 0000000..069d7d8 --- /dev/null +++ b/tests/test_fileio_read_other_formats.py @@ -0,0 +1,26 @@ +import pytest + +from pyomeca import Analogs, Markers + +from ._constants import ( + ANALOGS_CSV, + ANALOGS_MOT, + ANALOGS_STO, + EXPECTED_VALUES, + MARKERS_TRC, +) +from .utils import is_expected_array + + +def test_read_sto(): + is_expected_array(Analogs.from_sto(ANALOGS_STO), **EXPECTED_VALUES[62]) + with pytest.raises(IndexError): + Analogs.from_sto(ANALOGS_CSV) + + +def test_read_mot(): + is_expected_array(Analogs.from_mot(ANALOGS_MOT), **EXPECTED_VALUES[63]) + + +def test_read_trc(): + is_expected_array(Markers.from_trc(MARKERS_TRC), **EXPECTED_VALUES[64]) diff --git a/tests/test_fileio_write.py b/tests/test_fileio_write.py new file mode 100644 index 0000000..b4cc0f6 --- /dev/null +++ b/tests/test_fileio_write.py @@ -0,0 +1,43 @@ +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest +import xarray as xr +from scipy.io import loadmat + +from tests._constants import ANALOGS_DATA, MARKERS_DATA + + +@pytest.mark.parametrize("data", [ANALOGS_DATA, MARKERS_DATA[:, :-1, :]]) +@pytest.mark.parametrize("wide", [True, False]) +def test_write_csv(data: xr.DataArray, wide: bool): + temp_filename = Path(".") / "temp.csv" + data.meca.to_csv(filename=temp_filename, wide=wide) + + if wide: + if data.ndim > 2: + return + newly_created_file = ( + pd.read_csv(temp_filename, index_col="time") + .stack() + .to_xarray() + .rename({"level_1": "channel"}) + .T + ) + else: + newly_created_file = pd.read_csv(temp_filename, index_col=data.dims)[ + data.name + ].to_xarray() + data, newly_created_file = xr.align(data, newly_created_file) + np.testing.assert_array_almost_equal(data, newly_created_file, decimal=1) + temp_filename.unlink() + + +@pytest.mark.parametrize("data", [ANALOGS_DATA, MARKERS_DATA]) +def test_write_matlab(data: xr.DataArray): + temp_filename = Path(".") / "temp.mat" + data.meca.to_matlab(filename=temp_filename) + newly_created_file = loadmat(temp_filename)["data"] + np.testing.assert_array_equal(data, newly_created_file) + temp_filename.unlink() diff --git a/tests/test_object_creation.py b/tests/test_object_creation.py new file mode 100644 index 0000000..9dbf673 --- /dev/null +++ b/tests/test_object_creation.py @@ -0,0 +1,80 @@ +import numpy as np +import pytest +import xarray as xr + +from pyomeca import Analogs, Angles, Markers, Rototrans + +from ._constants import ANALOGS_DATA, EXPECTED_VALUES, MARKERS_DATA +from .utils import is_expected_array + + +def test_analogs_creation(): + dims = ("channel", "time") + array = Analogs() + np.testing.assert_array_equal(x=array, y=xr.DataArray()) + assert array.dims == dims + + array = Analogs(ANALOGS_DATA.values) + is_expected_array(array, **EXPECTED_VALUES[56]) + + size = 10, 100 + array = Analogs.from_random_data(size=size) + assert array.shape == size + assert array.dims == dims + + with pytest.raises(ValueError): + Analogs(MARKERS_DATA) + + +def test_markers_creation(): + dims = ("axis", "channel", "time") + array = Markers() + np.testing.assert_array_equal(x=array, y=xr.DataArray()) + assert array.dims == dims + + array = Markers(MARKERS_DATA.values) + is_expected_array(array, **EXPECTED_VALUES[57]) + + size = 3, 10, 100 + array = Markers.from_random_data(size=size) + assert array.shape == (4, size[1], size[2]) + assert array.dims == dims + + with pytest.raises(ValueError): + Markers(ANALOGS_DATA) + + +def test_angles_creation(): + dims = ("axis", "channel", "time") + array = Angles() + np.testing.assert_array_equal(x=array, y=xr.DataArray()) + assert array.dims == dims + + array = Angles(MARKERS_DATA.values, time=MARKERS_DATA.time) + is_expected_array(array, **EXPECTED_VALUES[57]) + + size = 10, 10, 100 + array = Angles.from_random_data(size=size) + assert array.shape == size + assert array.dims == dims + + with pytest.raises(ValueError): + Angles(ANALOGS_DATA) + + +def test_rototrans_creation(): + dims = ("row", "col", "time") + array = Rototrans() + np.testing.assert_array_equal(x=array, y=xr.DataArray(np.eye(4)[..., np.newaxis])) + assert array.dims == dims + + array = Rototrans(MARKERS_DATA.values, time=MARKERS_DATA.time) + is_expected_array(array, **EXPECTED_VALUES[67]) + + size = 4, 4, 100 + array = Rototrans.from_random_data(size=size) + assert array.shape == size + assert array.dims == dims + + with pytest.raises(ValueError): + Angles(ANALOGS_DATA) diff --git a/tests/test_processing_algebra.py b/tests/test_processing_algebra.py new file mode 100644 index 0000000..a1608c8 --- /dev/null +++ b/tests/test_processing_algebra.py @@ -0,0 +1,105 @@ +import numpy as np + +from pyomeca import Analogs, Markers +from tests._constants import ANALOGS_DATA, EXPECTED_VALUES, MARKERS_DATA +from tests.utils import is_expected_array + + +def test_proc_abs(): + is_expected_array(ANALOGS_DATA.meca.abs(), **EXPECTED_VALUES[1]) + is_expected_array(MARKERS_DATA.meca.abs(), **EXPECTED_VALUES[2]) + + +def test_proc_matmul(): + np.random.seed(42) # restard the seed + random_markers_1 = Markers.from_random_data() + random_markers_2 = Markers.from_random_data() + markers_matmul = random_markers_1.meca.matmul(random_markers_2) + ref_markers_matmul = random_markers_1 @ random_markers_2 + np.testing.assert_almost_equal(markers_matmul, -33729.52497131, decimal=6) + np.testing.assert_almost_equal(markers_matmul, ref_markers_matmul, decimal=6) + + +def test_proc_square_sqrt(): + is_expected_array(MARKERS_DATA.meca.square().meca.sqrt(), **EXPECTED_VALUES[3]) + + is_expected_array(ANALOGS_DATA.meca.square().meca.sqrt(), **EXPECTED_VALUES[4]) + + +def test_proc_norm(): + n_frames = 100 + n_markers = 10 + m = Markers(np.random.rand(3, n_markers, n_frames)) + + # norm by hand + expected_norm = np.linalg.norm(m[:3, ...], axis=0) + + # norm with pyomeca + computed_norm = m.meca.norm(dim="axis") + + np.testing.assert_almost_equal(computed_norm, expected_norm, decimal=10) + + is_expected_array(MARKERS_DATA.meca.norm(dim="axis"), **EXPECTED_VALUES[44]) + is_expected_array(MARKERS_DATA.meca.norm(dim="channel"), **EXPECTED_VALUES[45]) + is_expected_array(MARKERS_DATA.meca.norm(dim="time"), **EXPECTED_VALUES[46]) + + is_expected_array(ANALOGS_DATA.meca.norm(dim="channel"), **EXPECTED_VALUES[47]) + is_expected_array(ANALOGS_DATA.meca.norm(dim="time"), **EXPECTED_VALUES[48]) + + +def test_proc_norm_marker(): + n_frames = 100 + n_markers = 10 + random_marker = Markers.from_random_data(size=(3, n_markers, n_frames)) + + norm = random_marker.meca.norm(dim="axis") + + norm_without_ones = random_marker.drop_sel(axis="ones").meca.norm(dim="axis") + + np.testing.assert_array_equal(norm, norm_without_ones) + + expected_norm = np.ndarray((n_markers, n_frames)) + for marker in range(n_markers): + for frame in range(n_frames): + expected_norm[marker, frame] = np.sqrt( + random_marker[0:3, marker, frame].dot(random_marker[0:3, marker, frame]) + ) + + np.testing.assert_array_equal(norm, expected_norm) + + +def test_proc_rms(): + m = MARKERS_DATA.meca.rms() + a = ANALOGS_DATA.meca.rms() + + np.testing.assert_array_almost_equal(m, 496.31764559, decimal=6) + np.testing.assert_array_almost_equal(a, 0.00011321, decimal=6) + + +def test_proc_center(): + is_expected_array(MARKERS_DATA.meca.center(), **EXPECTED_VALUES[5]) + is_expected_array( + MARKERS_DATA.meca.center(MARKERS_DATA.isel(time=0)), **EXPECTED_VALUES[6] + ) + + is_expected_array(ANALOGS_DATA.meca.center(), **EXPECTED_VALUES[7]) + is_expected_array(ANALOGS_DATA.meca.center(mu=2), **EXPECTED_VALUES[8]) + is_expected_array( + ANALOGS_DATA.meca.center(ANALOGS_DATA.isel(time=0)), **EXPECTED_VALUES[9] + ) + + +def test_proc_normalize(): + is_expected_array(MARKERS_DATA.meca.normalize(), **EXPECTED_VALUES[20]) + is_expected_array(MARKERS_DATA.meca.normalize(scale=1), **EXPECTED_VALUES[21]) + is_expected_array( + MARKERS_DATA.meca.normalize(ref=MARKERS_DATA.sel(time=5.76)), + **EXPECTED_VALUES[22] + ) + + is_expected_array(ANALOGS_DATA.meca.normalize(), **EXPECTED_VALUES[23]) + is_expected_array(ANALOGS_DATA.meca.normalize(scale=1), **EXPECTED_VALUES[24]) + is_expected_array( + ANALOGS_DATA.meca.normalize(ref=ANALOGS_DATA.sel(time=5.76)), + **EXPECTED_VALUES[25] + ) diff --git a/tests/test_processing_filter.py b/tests/test_processing_filter.py new file mode 100644 index 0000000..6507299 --- /dev/null +++ b/tests/test_processing_filter.py @@ -0,0 +1,50 @@ +import pytest + +from pyomeca import Analogs +from tests._constants import ANALOGS_DATA, EXPECTED_VALUES, MARKERS_DATA +from tests.utils import is_expected_array + + +def test_proc_filters(): + freq = ANALOGS_DATA.rate + order = 2 + + is_expected_array( + ANALOGS_DATA.meca.low_pass(order=order, cutoff=5, freq=freq), + **EXPECTED_VALUES[32], + ) + is_expected_array( + ANALOGS_DATA.meca.low_pass(order=order, cutoff=5), **EXPECTED_VALUES[32], + ) + is_expected_array( + ANALOGS_DATA.meca.high_pass(order=order, cutoff=100), **EXPECTED_VALUES[33], + ) + is_expected_array( + ANALOGS_DATA.meca.band_pass(order=order, cutoff=[10, 200]), + **EXPECTED_VALUES[34], + ) + is_expected_array( + ANALOGS_DATA.meca.band_stop(order=order, cutoff=[40, 60]), + **EXPECTED_VALUES[35], + ) + + freq = MARKERS_DATA.rate + is_expected_array( + MARKERS_DATA.meca.low_pass(freq=freq, order=order, cutoff=5), + **EXPECTED_VALUES[36], + ) + is_expected_array( + MARKERS_DATA.meca.low_pass(order=order, cutoff=5), **EXPECTED_VALUES[36], + ) + is_expected_array( + MARKERS_DATA.meca.high_pass(order=order, cutoff=10), **EXPECTED_VALUES[37], + ) + is_expected_array( + MARKERS_DATA.meca.band_pass(order=order, cutoff=[1, 10]), **EXPECTED_VALUES[38], + ) + is_expected_array( + MARKERS_DATA.meca.band_stop(order=order, cutoff=[5, 6]), **EXPECTED_VALUES[39], + ) + + with pytest.raises(ValueError): + Analogs.from_random_data().meca.band_stop(order=order, cutoff=[5, 6]) diff --git a/tests/test_processing_interp.py b/tests/test_processing_interp.py new file mode 100644 index 0000000..5d8b97a --- /dev/null +++ b/tests/test_processing_interp.py @@ -0,0 +1,27 @@ +import numpy as np + +from tests._constants import ANALOGS_DATA, EXPECTED_VALUES, MARKERS_DATA +from tests.utils import is_expected_array + + +def test_proc_time_normalize(): + is_expected_array(MARKERS_DATA.meca.time_normalize(), **EXPECTED_VALUES[26]) + is_expected_array( + MARKERS_DATA.meca.time_normalize(n_frames=1000), **EXPECTED_VALUES[27] + ) + time_vector = np.linspace(MARKERS_DATA.time[0], MARKERS_DATA.time[100], 100) + is_expected_array( + MARKERS_DATA.meca.time_normalize(time_vector=time_vector), **EXPECTED_VALUES[28] + ) + is_expected_array( + MARKERS_DATA.meca.time_normalize(norm_time=True).time, **EXPECTED_VALUES[55] + ) + + is_expected_array(ANALOGS_DATA.meca.time_normalize(), **EXPECTED_VALUES[29]) + is_expected_array( + ANALOGS_DATA.meca.time_normalize(n_frames=1000), **EXPECTED_VALUES[30] + ) + time_vector = np.linspace(ANALOGS_DATA.time[0], ANALOGS_DATA.time[100], 100) + is_expected_array( + ANALOGS_DATA.meca.time_normalize(time_vector=time_vector), **EXPECTED_VALUES[31] + ) diff --git a/tests/test_processing_marker.py b/tests/test_processing_marker.py new file mode 100644 index 0000000..58176ce --- /dev/null +++ b/tests/test_processing_marker.py @@ -0,0 +1,51 @@ +import numpy as np +import pytest + +from pyomeca import Angles, Markers, Rototrans + + +def test_rotate(): + n_frames = 100 + n_markers = 10 + + angles = Angles.from_random_data(size=(3, 1, n_frames)) + rt = Rototrans.from_euler_angles(angles, "xyz") + markers = Markers.from_random_data(size=(3, n_markers, n_frames)) + + rotated_markers = Markers.from_rototrans(markers, rt) + + expected_rotated_marker = np.ndarray((4, n_markers, n_frames)) + for marker in range(n_markers): + for frame in range(n_frames): + expected_rotated_marker[:, marker, frame] = np.dot( + rt.isel(time=frame), markers.isel(channel=marker, time=frame), + ) + + np.testing.assert_array_almost_equal( + rotated_markers, expected_rotated_marker, decimal=10 + ) + + rotated_markers = Markers.from_rototrans(markers.isel(time=0), rt.isel(time=0)) + expected_rotated_marker = np.ndarray(rotated_markers.shape) + for marker in range(n_markers): + expected_rotated_marker[:, marker] = np.dot( + rt.isel(time=0), markers.isel(channel=marker, time=0) + ) + + np.testing.assert_array_almost_equal( + rotated_markers, expected_rotated_marker, decimal=10 + ) + + rotated_markers = Markers.from_rototrans(markers, rt.isel(time=0)) + expected_rotated_marker = np.ndarray(rotated_markers.shape) + for marker in range(n_markers): + expected_rotated_marker[:, marker] = np.dot( + rt.isel(time=0), markers.isel(channel=marker) + ) + + np.testing.assert_array_almost_equal( + rotated_markers, expected_rotated_marker, decimal=10 + ) + + with pytest.raises(ValueError): + Markers.from_rototrans(markers.isel(time=0), rt) diff --git a/tests/test_processing_misc.py b/tests/test_processing_misc.py new file mode 100644 index 0000000..37d5240 --- /dev/null +++ b/tests/test_processing_misc.py @@ -0,0 +1,69 @@ +import numpy as np +import pytest +import xarray as xr + +from pyomeca.processing import misc +from tests._constants import ANALOGS_DATA, EXPECTED_VALUES, MARKERS_DATA +from tests.utils import is_expected_array + + +def test_proc_fft(): + is_expected_array( + ANALOGS_DATA.meca.fft(freq=ANALOGS_DATA.rate), **EXPECTED_VALUES[40] + ) + is_expected_array( + ANALOGS_DATA.meca.fft(freq=ANALOGS_DATA.rate, only_positive=False), + **EXPECTED_VALUES[41] + ) + + is_expected_array( + MARKERS_DATA.meca.fft(freq=ANALOGS_DATA.rate), **EXPECTED_VALUES[42] + ) + is_expected_array( + MARKERS_DATA.meca.fft(freq=ANALOGS_DATA.rate, only_positive=False), + **EXPECTED_VALUES[43] + ) + + +def test_proc_detect_onset(): + m = MARKERS_DATA[0, 0, :] + r = xr.DataArray(m.meca.detect_onset(threshold=m.mean() + m.std())) + is_expected_array(r, **EXPECTED_VALUES[49]) + + r = xr.DataArray( + m.meca.detect_onset( + threshold=m.mean(), n_below=10, threshold2=m.mean() + m.std() + ) + ) + is_expected_array(r, **EXPECTED_VALUES[50]) + + np.testing.assert_array_equal(x=m.meca.detect_onset(threshold=m.mean() * 10), y=0) + + with pytest.raises(ValueError): + MARKERS_DATA[0, :, :].meca.detect_onset(threshold=0) + + with pytest.raises(ValueError): + MARKERS_DATA[:, :, :].meca.detect_onset(threshold=0) + + +def test_proc_detect_outliers(): + is_expected_array( + MARKERS_DATA.meca.detect_outliers(threshold=3), **EXPECTED_VALUES[51] + ) + is_expected_array( + MARKERS_DATA.meca.detect_outliers(threshold=1), **EXPECTED_VALUES[52] + ) + + is_expected_array( + ANALOGS_DATA.meca.detect_outliers(threshold=3), **EXPECTED_VALUES[53] + ) + is_expected_array( + ANALOGS_DATA.meca.detect_outliers(threshold=1), **EXPECTED_VALUES[54] + ) + + +def test_has_correct_name(): + misc.has_correct_name(MARKERS_DATA, "markers") + + with pytest.raises(ValueError): + misc.has_correct_name(MARKERS_DATA, "rototrans") diff --git a/tests/test_processing_rt.py b/tests/test_processing_rt.py new file mode 100644 index 0000000..f5bd045 --- /dev/null +++ b/tests/test_processing_rt.py @@ -0,0 +1,251 @@ +from itertools import permutations + +import numpy as np +import pytest + +from pyomeca import Angles, Markers, Rototrans + +SEQ = ( + ["".join(p) for i in range(1, 4) for p in permutations("xyz", i)] + + ["zyzz"] + + ["zxz"] +) +SEQ = [s for s in SEQ if s not in ["yxz"]] +EPSILON = 1e-12 +ANGLES = Angles(np.random.rand(4, 1, 100)) + + +@pytest.mark.parametrize("seq", SEQ) +def test_euler2rot_rot2euleur(seq, angles=ANGLES, epsilon=EPSILON): + if seq == "zyzz": + angles_to_test = angles[:3, ...] + else: + angles_to_test = angles[: len(seq), ...] + r = Rototrans.from_euler_angles(angles=angles_to_test, angle_sequence=seq) + a = Angles.from_rototrans(rt=r, angle_sequence=seq) + + np.testing.assert_array_less((a - angles_to_test).meca.abs().sum(), epsilon) + + +def test_construct_rt(): + eye = Rototrans() + np.testing.assert_equal(eye.time.size, 1) + np.testing.assert_equal(eye.sel(time=0), np.eye(4)) + + eye = Rototrans.from_euler_angles() + np.testing.assert_equal(eye.time.size, 1) + np.testing.assert_equal(eye.sel(time=0), np.eye(4)) + + # Test the way to create a rt, but not when providing bot angles and sequence + nb_frames = 10 + random_vector = Angles(np.random.rand(3, 1, nb_frames)) + + # with angles + rt_random_angles = Rototrans.from_euler_angles( + angles=random_vector, angle_sequence="xyz" + ) + np.testing.assert_equal(rt_random_angles.time.size, nb_frames) + np.testing.assert_equal( + rt_random_angles[:-1, -1:, :], np.zeros((3, 1, nb_frames)) + ) # Translation is 0 + + # with translation + rt_random_translation = Rototrans.from_euler_angles(translations=random_vector) + np.testing.assert_equal(rt_random_translation.time.size, nb_frames) + np.testing.assert_equal( + rt_random_translation[:3, :3, :], + np.repeat(np.eye(3)[:, :, np.newaxis], nb_frames, axis=2), + ) # rotation is eye3 + np.arange(0, rt_random_angles.time.size / 0.5, 1 / 0.5) + + rt_with_time = Rototrans( + rt_random_angles, time=np.arange(0, rt_random_angles.time.size / 100, 1 / 100), + ) + assert rt_with_time.time[-1] == 0.09 + + with pytest.raises(IndexError): + Rototrans(data=np.zeros(1)) + + with pytest.raises(IndexError): + Rototrans.from_euler_angles( + angles=random_vector[..., :5], + translations=random_vector, + angle_sequence="x", + ) + + with pytest.raises(IndexError): + Rototrans.from_euler_angles(angles=random_vector, angle_sequence="x") + + with pytest.raises(ValueError): + Rototrans.from_euler_angles(angles=random_vector, angle_sequence="nop") + + +def test_rt_from_markers(): + all_m = Markers.from_random_data() + + rt_xy = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 1]), + axis_2=all_m.isel(channel=[0, 2]), + axes_name="xy", + axis_to_recalculate="y", + ) + + rt_yx = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 2]), + axis_2=all_m.isel(channel=[0, 1]), + axes_name="yx", + axis_to_recalculate="y", + ) + + rt_xy_x_recalc = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 1]), + axis_2=all_m.isel(channel=[0, 2]), + axes_name="yx", + axis_to_recalculate="x", + ) + rt_xy_x_recalc = rt_xy_x_recalc.isel(col=[1, 0, 2, 3]) + rt_xy_x_recalc[:, 2, :] = -rt_xy_x_recalc[:, 2, :] + + rt_yz = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 1]), + axis_2=all_m.isel(channel=[0, 2]), + axes_name="yz", + axis_to_recalculate="z", + ) + + rt_zy = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 2]), + axis_2=all_m.isel(channel=[0, 1]), + axes_name="zy", + axis_to_recalculate="z", + ) + rt_xy_from_yz = rt_yz.isel(col=[1, 2, 0, 3]) + + rt_xz = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 1]), + axis_2=all_m.isel(channel=[0, 2]), + axes_name="xz", + axis_to_recalculate="z", + ) + + rt_zx = Rototrans.from_markers( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 2]), + axis_2=all_m.isel(channel=[0, 1]), + axes_name="zx", + axis_to_recalculate="z", + ) + rt_xy_from_zx = rt_xz.isel(col=[0, 2, 1, 3]) + rt_xy_from_zx[:, 2, :] = -rt_xy_from_zx[:, 2, :] + + np.testing.assert_array_equal(rt_xy, rt_xy_x_recalc) + np.testing.assert_array_equal(rt_xy, rt_yx) + np.testing.assert_array_equal(rt_yz, rt_zy) + np.testing.assert_array_equal(rt_xz, rt_zx) + np.testing.assert_array_equal(rt_xy, rt_xy_from_yz) + np.testing.assert_array_equal(rt_xy, rt_xy_from_zx) + + # Produce one that we know the solution + ref_m = Markers(np.array(((1, 2, 3), (4, 5, 6), (6, 5, 4))).T[:, :, np.newaxis]) + rt_xy_from_known_m = Rototrans.from_markers( + origin=ref_m.isel(channel=[0]), + axis_1=ref_m.isel(channel=[0, 1]), + axis_2=ref_m.isel(channel=[0, 2]), + axes_name="xy", + axis_to_recalculate="y", + ) + + rt_xy_expected = Rototrans( + np.array( + [ + [0.5773502691896257, 0.7071067811865475, -0.408248290463863, 1.0], + [0.5773502691896257, 0.0, 0.816496580927726, 2.0], + [0.5773502691896257, -0.7071067811865475, -0.408248290463863, 3.0], + [0, 0, 0, 1.0], + ] + ) + ) + + np.testing.assert_array_equal(rt_xy_from_known_m, rt_xy_expected) + + exception_default_params = dict( + origin=all_m.isel(channel=[0]), + axis_1=all_m.isel(channel=[0, 1]), + axis_2=all_m.isel(channel=[0, 2]), + axes_name="xy", + axis_to_recalculate="y", + ) + with pytest.raises(ValueError): + Rototrans.from_markers( + **{**exception_default_params, **dict(origin=all_m.isel(channel=[0, 1]))} + ) + + with pytest.raises(ValueError): + Rototrans.from_markers( + **{**exception_default_params, **dict(axis_1=all_m.isel(channel=[0]))} + ) + + with pytest.raises(ValueError): + Rototrans.from_markers( + **{**exception_default_params, **dict(axis_2=all_m.isel(channel=[0]))} + ) + + with pytest.raises(ValueError): + Rototrans.from_markers( + **{ + **exception_default_params, + **dict(axis_1=all_m.isel(channel=[0, 1], time=slice(None, 50))), + } + ) + + with pytest.raises(ValueError): + Rototrans.from_markers(**{**exception_default_params, **dict(axes_name="yyz")}) + + with pytest.raises(ValueError): + Rototrans.from_markers(**{**exception_default_params, **dict(axes_name="xxz")}) + + with pytest.raises(ValueError): + Rototrans.from_markers(**{**exception_default_params, **dict(axes_name="zzz")}) + + with pytest.raises(ValueError): + Rototrans.from_markers( + **{**exception_default_params, **dict(axis_to_recalculate="h")} + ) + + +def test_rt_transpose(): + rt = Rototrans.from_random_data() + rt_t = Rototrans.from_transposed_rototrans(rt) + + rt_t_expected = np.zeros((4, 4, rt.time.size)) + rt_t_expected[3, 3, :] = 1 + for row in range(rt.row.size): + for col in range(rt.col.size): + for frame in range(rt.time.size): + rt_t_expected[col, row, frame] = rt[row, col, frame] + + for frame in range(rt.time.size): + rt_t_expected[:3, 3, frame] = -rt_t_expected[:3, :3, frame].dot( + rt[:3, 3, frame] + ) + + np.testing.assert_array_almost_equal(rt_t, rt_t_expected, decimal=10) + + +def test_average_rt(): + angles = Angles(np.random.rand(3, 1, 100)) + seq = "xyz" + + rt = Rototrans.from_euler_angles(angles, seq) + rt_mean = Rototrans.from_averaged_rototrans(rt) + angles_mean = Angles.from_rototrans(rt_mean, seq).isel(time=0) + + angles_mean_ref = Angles.from_rototrans(rt, seq).mean(dim="time") + + np.testing.assert_array_almost_equal(angles_mean, angles_mean_ref, decimal=2) diff --git a/tests/test_rototrans.py b/tests/test_rototrans.py deleted file mode 100644 index 50c9e8d..0000000 --- a/tests/test_rototrans.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Test for euler to rot and rot to euler -""" -import numpy as np -import pytest - -from pyomeca import RotoTrans, FrameDependentNpArray - -# Define all the possible angle_sequence to tests -SEQ = [ - "x", - "y", - "z", - "xy", - "xz", - "yx", - "yz", - "zx", - "zy", - "xyz", - "xzy", - "yxz", - "yzx", - "zxy", - "zyx", - "zyzz", -] -# If the difference between the initial and the final angles are less than epsilon, tests is success -EPSILON = 1e-12 -# Define some random data to tests -ANGLES = FrameDependentNpArray(np.random.rand(40, 1, 100)) - - -def test_construct_rt(): - # Test the ways to create an eye matrix - eye = RotoTrans() - np.testing.assert_equal(eye.get_num_frames(), 1) - np.testing.assert_equal(eye[:, :, 0], np.eye(4)) - - eye = RotoTrans(RotoTrans.rt_from_euler_angles()) - np.testing.assert_equal(eye.get_num_frames(), 1) - np.testing.assert_equal(eye[:, :, 0], np.eye(4)) - - # Test the way to create a rt, but not when providing bot angles and sequence - # this is done in test_euler2rot_rot2euler - nb_frames = 10 - random_vector = FrameDependentNpArray(np.random.rand(3, 1, nb_frames)) - - random_from_angles = RotoTrans( - RotoTrans.rt_from_euler_angles(angles=random_vector, angle_sequence="xyz") - ) - np.testing.assert_equal(random_from_angles.get_num_frames(), nb_frames) - np.testing.assert_equal( - random_from_angles[0:3, 3, :], np.zeros((3, 1, nb_frames)) - ) # Translation is 0 - - random_from_translations = RotoTrans( - RotoTrans.rt_from_euler_angles(translations=random_vector) - ) - np.testing.assert_equal(random_from_translations.get_num_frames(), nb_frames) - np.testing.assert_equal( - random_from_translations[0:3, 0:3, :], - np.repeat(np.eye(3)[:, :, np.newaxis], nb_frames, axis=2), - ) # rotation is eye3 - - -@pytest.mark.parametrize("seq", SEQ) -@pytest.mark.parametrize("angles", [ANGLES]) -@pytest.mark.parametrize("epsilon", [EPSILON]) -def test_euler2rot_rot2euler(seq, angles, epsilon): - """Test euler to RotoTrans and RotoTrans to euler.""" - # Extract the right amount of angle relative to sequence length - if seq != "zyzz": - angles_to_test = angles[0 : len(seq), :, :] - else: - angles_to_test = angles[0:3, :, :] - # Get a RotoTrans from euler angles - p = RotoTrans(angles=angles_to_test, angle_sequence=seq) - # Get euler angles back from RotoTrans - a = p.get_euler_angles(angle_sequence=seq) - - np.testing.assert_array_less((a - angles_to_test).sum(), epsilon) - - -@pytest.mark.parametrize("angles", [ANGLES]) -def test_rt_mean(angles): - seq = "xyz" - angles_to_test = angles[0:3, :, :] - p = RotoTrans(angles=angles_to_test, angle_sequence=seq) - angles_mean = p.mean().get_euler_angles(seq) - - # Test the difference with a very relax tolerance since angles_to_compare is false by definition - # but should not be too far most of the time - angles_to_compare = p.get_euler_angles(angle_sequence=seq).mean() - np.testing.assert_array_less((angles_mean - angles_to_compare).sum(), 1e-1) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..bfdea1a --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,179 @@ +import inspect +import json +import re + +import numpy as np +import xarray as xr + +mkdocs_server = "http://127.0.0.1:8000" + + +class DocStringError(Exception): + pass + + +def is_expected_array( + array, + shape_val: tuple, + first_last_val: tuple, + mean_val: float, + median_val: float, + sum_val: float, + nans_val: int, + decimal: int = 6, +): + np.testing.assert_array_equal( + x=array.shape, y=shape_val, err_msg="Shape does not match" + ) + raveled = array.values.ravel() + np.testing.assert_array_almost_equal( + x=raveled[0], + y=first_last_val[0], + err_msg="First value does not match", + decimal=decimal, + ) + np.testing.assert_array_almost_equal( + x=raveled[-1], + y=first_last_val[-1], + err_msg="Last value does not match", + decimal=decimal, + ) + np.testing.assert_array_almost_equal( + x=array.mean(), y=mean_val, decimal=decimal, err_msg="Mean does not match" + ) + np.testing.assert_array_almost_equal( + x=array.median(skipna=True), + y=median_val, + decimal=decimal, + err_msg="Median does not match", + ) + np.testing.assert_allclose( + actual=array.sum(), desired=sum_val, rtol=0.05, err_msg="Sum does not match" + ) + np.testing.assert_array_equal( + x=array.isnull().sum(), y=nans_val, err_msg="Nans value value does not match" + ) + + +def print_expected_values(array: xr.DataArray): + shape_val = array.shape + print(f"shape_val={shape_val}") + + ravel = array.values.ravel() + first_last_val = ravel[0], ravel[-1] + print(f"first_last_val={first_last_val}") + + mean_val = array.mean().item() + print(f"mean_val={mean_val}") + + median_val = array.median(skipna=True).item() + print(f"median_val={median_val}") + + sum_val = array.sum().item() + print(f"sum_val={sum_val}") + + nans_val = array.isnull().sum().item() + print(f"nans_val={nans_val}") + + +def is_function_or_method_or_new(method): + return (inspect.isfunction(method) or inspect.ismethod(method)) and ( + method.__name__[0] != "_" or method.__name__ == "__new__" + ) + + +def get_available_methods(module): + return [ + method_obj + for class_name, class_obj in inspect.getmembers(module, inspect.isclass) + for method_name, method_obj in inspect.getmembers(class_obj) + if is_function_or_method_or_new(method_obj) + ] + + +def do_we_generate_doc_files(): + try: + import requests + + try: + return requests.get(mkdocs_server).status_code == 200 + except requests.exceptions.ConnectionError: + return False + except ModuleNotFoundError: + return False + + +def function_has_return(func): + """Caution: this will return True if the function contains the word 'return'""" + lines, _ = inspect.getsourcelines(func) + return any("return" in line for line in lines) + + +def extract_code_blocks_from_md( + docstring: str, start_code_block: str = "```python", end_code_block: str = "```" +) -> str: + return inspect.cleandoc( + "\n".join( + re.findall( + fr"{start_code_block}(.*?){end_code_block}", docstring, re.DOTALL + ) + ) + ) + + +def generate_api_json(module): + api_json = { + "name": "pyomeca", + "docstring": "

Base module

", + "link": "/", + "children": [], + } + for class_name, class_obj in inspect.getmembers(module, inspect.isclass): + class_dict = { + "name": class_name, + "link": f"/api/{class_obj.__module__.split('.')[-1]}/#{class_obj.__module__}.{class_name}", + "children": [], + } + if class_name == "DataArrayAccessor": + class_dict["docstring"] = f"

{class_obj.__doc__}

" + for method_name, method_obj in inspect.getmembers(class_obj): + if is_function_or_method_or_new(method_obj): + method_dict = { + "name": method_name, + "link": f"{class_dict['link']}.{method_name}", + "value": 1, + } + method_docstring = get_method_generated_docstring(method_dict) + if method_name == "__new__": + class_dict["docstring"] = method_docstring + else: + method_dict["docstring"] = method_docstring + class_dict["children"].append(method_dict) + + api_json["children"].append(class_dict) + + with open("docs/api/api.json", "w") as api_file: + json.dump(api_json, api_file, indent=2) + + +def get_method_generated_docstring(method_dict): + import requests + from bs4 import BeautifulSoup + + to_delete = '

Example

' + html = requests.get(f"{mkdocs_server}{method_dict['link']}") + html.raise_for_status() + soup = BeautifulSoup(html.text) + doc_bloc = soup.find( + "h3", {"id": method_dict["link"].split("#")[-1]} + ).find_next_sibling() + description = f"{doc_bloc.find('p')}" + example = "".join( + [f"{i}" for i in doc_bloc.find("div", {"class": "admonition example"}).children] + ) + generated_docstring = description + example + if generated_docstring: + return generated_docstring.replace(to_delete, "").replace( + "