├── .coveragerc ├── .editorconfig ├── .gitchangelog.rc ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── build_wheels.yml │ └── codecov.yml ├── .gitignore ├── AUTHORS.rst ├── CHANGELOG.md ├── CONTRIBUTING.rst ├── COPYING ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── CASE.SENSITIVE.FS ├── Makefile ├── apidocs-requirements.txt ├── conf.py ├── index.rst └── make.bat ├── dtw ├── __init__.py ├── __main__.py ├── _backtrack.py ├── _dtw_utils.pyx ├── _globalCostMatrix.py ├── countPaths.py ├── data │ ├── README.txt │ ├── aami3a.csv │ └── aami3b.csv ├── dtw.py ├── dtwPlot.py ├── dtw_core.c ├── dtw_core.h ├── dtw_test_data.py ├── mvm.py ├── stepPattern.py ├── warp.py ├── warpArea.py └── window.py ├── maintainer ├── README.maintainer ├── examples │ ├── ex_aami.py.txt │ ├── ex_countPaths.py.txt │ ├── ex_dtw.py.txt │ ├── ex_dtwPlotDensity.py.txt │ ├── ex_dtwWindowingFunctions.py.txt │ ├── ex_mvmStepPattern.py.txt │ ├── ex_stepPattern.py.txt │ ├── ex_warp.py.txt │ └── ex_warpArea.py.txt └── roxypick.py ├── pyproject.toml ├── setup.cfg ├── setup.py └── tests ├── query.csv ├── reference.csv ├── test_cli.py ├── test_countPaths.py ├── test_cran.py ├── test_doctests.py ├── test_dtw.py ├── test_dtw_s.py └── test_issues.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source=dtw 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.gitchangelog.rc: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | ## 3 | ## Format 4 | ## 5 | ## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] 6 | ## 7 | ## Description 8 | ## 9 | ## ACTION is one of 'chg', 'fix', 'new' 10 | ## 11 | ## Is WHAT the change is about. 12 | ## 13 | ## 'chg' is for refactor, small improvement, cosmetic changes... 14 | ## 'fix' is for bug fixes 15 | ## 'new' is for new features, big improvement 16 | ## 17 | ## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' 18 | ## 19 | ## Is WHO is concerned by the change. 20 | ## 21 | ## 'dev' is for developpers (API changes, refactors...) 22 | ## 'usr' is for final users (UI changes) 23 | ## 'pkg' is for packagers (packaging changes) 24 | ## 'test' is for testers (test only related changes) 25 | ## 'doc' is for doc guys (doc only changes) 26 | ## 27 | ## COMMIT_MSG is ... well ... the commit message itself. 28 | ## 29 | ## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' 30 | ## 31 | ## They are preceded with a '!' or a '@' (prefer the former, as the 32 | ## latter is wrongly interpreted in github.) Commonly used tags are: 33 | ## 34 | ## 'refactor' is obviously for refactoring code only 35 | ## 'minor' is for a very meaningless change (a typo, adding a comment) 36 | ## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) 37 | ## 'wip' is for partial functionality but complete subfunctionality. 38 | ## 39 | ## Example: 40 | ## 41 | ## new: usr: support of bazaar implemented 42 | ## chg: re-indentend some lines !cosmetic 43 | ## new: dev: updated code to be compatible with last version of killer lib. 44 | ## fix: pkg: updated year of licence coverage. 45 | ## new: test: added a bunch of test around user usability of feature X. 46 | ## fix: typo in spelling my name in comment. !minor 47 | ## 48 | ## Please note that multi-line commit message are supported, and only the 49 | ## first line will be considered as the "summary" of the commit message. So 50 | ## tags, and other rules only applies to the summary. The body of the commit 51 | ## message will be displayed in the changelog without reformatting. 52 | 53 | 54 | ## 55 | ## ``ignore_regexps`` is a line of regexps 56 | ## 57 | ## Any commit having its full commit message matching any regexp listed here 58 | ## will be ignored and won't be reported in the changelog. 59 | ## 60 | ignore_regexps = [ 61 | r'Bump version', 62 | r'^.{0,4}$', 63 | r'empty log message', 64 | 65 | r'@minor', r'!minor', 66 | r'@cosmetic', r'!cosmetic', 67 | r'@refactor', r'!refactor', 68 | r'@wip', r'!wip', 69 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', 70 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', 71 | r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', 72 | r'^$', ## ignore commits with empty messages 73 | ] 74 | 75 | 76 | ## ``section_regexps`` is a list of 2-tuples associating a string label and a 77 | ## list of regexp 78 | ## 79 | ## Commit messages will be classified in sections thanks to this. Section 80 | ## titles are the label, and a commit is classified under this section if any 81 | ## of the regexps associated is matching. 82 | ## 83 | ## Please note that ``section_regexps`` will only classify commits and won't 84 | ## make any changes to the contents. So you'll probably want to go check 85 | ## ``subject_process`` (or ``body_process``) to do some changes to the subject, 86 | ## whenever you are tweaking this variable. 87 | ## 88 | section_regexps = [ 89 | ('New', [ 90 | r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 91 | ]), 92 | ('Changes', [ 93 | r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 94 | ]), 95 | ('Fix', [ 96 | r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 97 | ]), 98 | 99 | ('Other', None ## Match all lines 100 | ), 101 | 102 | ] 103 | 104 | 105 | ## ``body_process`` is a callable 106 | ## 107 | ## This callable will be given the original body and result will 108 | ## be used in the changelog. 109 | ## 110 | ## Available constructs are: 111 | ## 112 | ## - any python callable that take one txt argument and return txt argument. 113 | ## 114 | ## - ReSub(pattern, replacement): will apply regexp substitution. 115 | ## 116 | ## - Indent(chars=" "): will indent the text with the prefix 117 | ## Please remember that template engines gets also to modify the text and 118 | ## will usually indent themselves the text if needed. 119 | ## 120 | ## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns 121 | ## 122 | ## - noop: do nothing 123 | ## 124 | ## - ucfirst: ensure the first letter is uppercase. 125 | ## (usually used in the ``subject_process`` pipeline) 126 | ## 127 | ## - final_dot: ensure text finishes with a dot 128 | ## (usually used in the ``subject_process`` pipeline) 129 | ## 130 | ## - strip: remove any spaces before or after the content of the string 131 | ## 132 | ## - SetIfEmpty(msg="No commit message."): will set the text to 133 | ## whatever given ``msg`` if the current text is empty. 134 | ## 135 | ## Additionally, you can `pipe` the provided filters, for instance: 136 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") 137 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') 138 | #body_process = noop 139 | body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip 140 | 141 | 142 | ## ``subject_process`` is a callable 143 | ## 144 | ## This callable will be given the original subject and result will 145 | ## be used in the changelog. 146 | ## 147 | ## Available constructs are those listed in ``body_process`` doc. 148 | subject_process = (strip | 149 | ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | 150 | SetIfEmpty("No commit message.") | ucfirst | final_dot) 151 | 152 | 153 | ## ``tag_filter_regexp`` is a regexp 154 | ## 155 | ## Tags that will be used for the changelog must match this regexp. 156 | ## 157 | tag_filter_regexp = r'^v[0-9]+\.[0-9]+(\.[0-9]+)?$' 158 | 159 | 160 | ## ``unreleased_version_label`` is a string or a callable that outputs a string 161 | ## 162 | ## This label will be used as the changelog Title of the last set of changes 163 | ## between last valid tag and HEAD if any. 164 | unreleased_version_label = "(unreleased)" 165 | 166 | 167 | ## ``output_engine`` is a callable 168 | ## 169 | ## This will change the output format of the generated changelog file 170 | ## 171 | ## Available choices are: 172 | ## 173 | ## - rest_py 174 | ## 175 | ## Legacy pure python engine, outputs ReSTructured text. 176 | ## This is the default. 177 | ## 178 | ## - mustache() 179 | ## 180 | ## Template name could be any of the available templates in 181 | ## ``templates/mustache/*.tpl``. 182 | ## Requires python package ``pystache``. 183 | ## Examples: 184 | ## - mustache("markdown") 185 | ## - mustache("restructuredtext") 186 | ## 187 | ## - makotemplate() 188 | ## 189 | ## Template name could be any of the available templates in 190 | ## ``templates/mako/*.tpl``. 191 | ## Requires python package ``mako``. 192 | ## Examples: 193 | ## - makotemplate("restructuredtext") 194 | ## 195 | output_engine = rest_py 196 | #output_engine = mustache("restructuredtext") 197 | #output_engine = mustache("markdown") 198 | #output_engine = makotemplate("restructuredtext") 199 | 200 | 201 | ## ``include_merge`` is a boolean 202 | ## 203 | ## This option tells git-log whether to include merge commits in the log. 204 | ## The default is to include them. 205 | include_merge = True 206 | 207 | 208 | ## ``log_encoding`` is a string identifier 209 | ## 210 | ## This option tells gitchangelog what encoding is outputed by ``git log``. 211 | ## The default is to be clever about it: it checks ``git config`` for 212 | ## ``i18n.logOutputEncoding``, and if not found will default to git's own 213 | ## default: ``utf-8``. 214 | #log_encoding = 'utf-8' 215 | 216 | 217 | ## ``publish`` is a callable 218 | ## 219 | ## Sets what ``gitchangelog`` should do with the output generated by 220 | ## the output engine. ``publish`` is a callable taking one argument 221 | ## that is an interator on lines from the output engine. 222 | ## 223 | ## Some helper callable are provided: 224 | ## 225 | ## Available choices are: 226 | ## 227 | ## - stdout 228 | ## 229 | ## Outputs directly to standard output 230 | ## (This is the default) 231 | ## 232 | ## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start()) 233 | ## 234 | ## Creates a callable that will parse given file for the given 235 | ## regex pattern and will insert the output in the file. 236 | ## ``idx`` is a callable that receive the matching object and 237 | ## must return a integer index point where to insert the 238 | ## the output in the file. Default is to return the position of 239 | ## the start of the matched string. 240 | ## 241 | ## - FileRegexSubst(file, pattern, replace, flags) 242 | ## 243 | ## Apply a replace inplace in the given file. Your regex pattern must 244 | ## take care of everything and might be more complex. Check the README 245 | ## for a complete copy-pastable example. 246 | ## 247 | # publish = FileInsertIntoFirstRegexMatch( 248 | # "CHANGELOG.rst", 249 | # r'/(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/', 250 | # idx=lambda m: m.start(1) 251 | # ) 252 | #publish = stdout 253 | 254 | 255 | ## ``revs`` is a list of callable or a list of string 256 | ## 257 | ## callable will be called to resolve as strings and allow dynamical 258 | ## computation of these. The result will be used as revisions for 259 | ## gitchangelog (as if directly stated on the command line). This allows 260 | ## to filter exaclty which commits will be read by gitchangelog. 261 | ## 262 | ## To get a full documentation on the format of these strings, please 263 | ## refer to the ``git rev-list`` arguments. There are many examples. 264 | ## 265 | ## Using callables is especially useful, for instance, if you 266 | ## are using gitchangelog to generate incrementally your changelog. 267 | ## 268 | ## Some helpers are provided, you can use them:: 269 | ## 270 | ## - FileFirstRegexMatch(file, pattern): will return a callable that will 271 | ## return the first string match for the given pattern in the given file. 272 | ## If you use named sub-patterns in your regex pattern, it'll output only 273 | ## the string matching the regex pattern named "rev". 274 | ## 275 | ## - Caret(rev): will return the rev prefixed by a "^", which is a 276 | ## way to remove the given revision and all its ancestor. 277 | ## 278 | ## Please note that if you provide a rev-list on the command line, it'll 279 | ## replace this value (which will then be ignored). 280 | ## 281 | ## If empty, then ``gitchangelog`` will act as it had to generate a full 282 | ## changelog. 283 | ## 284 | ## The default is to use all commits to make the changelog. 285 | #revs = ["^1.0.3", ] 286 | #revs = [ 287 | # Caret( 288 | # FileFirstRegexMatch( 289 | # "CHANGELOG.rst", 290 | # r"(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")), 291 | # "HEAD" 292 | #] 293 | revs = [] 294 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | *Important:* Please provide a self-contained example. A colab may also be ok. 17 | 18 | **Version** 19 | Please state the version of dtw-python (as reported by pip or conda). 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | # Check for updates to GitHub Actions every weekday 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/build_wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload to PyPI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_wheels: 7 | name: Build wheels on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest, macos-13, macos-14] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Build wheels 17 | uses: pypa/cibuildwheel@v2.23.2 18 | env: 19 | CIBW_SKIP: "cp36* pp* *musllinux*" 20 | CIBW_TEST_REQUIRES: pytest 21 | CIBW_TEST_COMMAND: "pytest {project}/tests" 22 | CIBW_ARCHS: auto64 23 | # CIBW_ARCHS_MACOS: "x86_64 universal2" 24 | # CIBW_ARCHS_WINDOWS: auto64 25 | # CIBW_ARCHS_LINUX: auto64 26 | # CIBW_TEST_SKIP: "cp311*" due to scipy not finding openblas to build from source. may be removed when 311 released. 27 | 28 | - uses: actions/upload-artifact@v4 29 | with: 30 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 31 | path: ./wheelhouse/*.whl 32 | 33 | 34 | 35 | make_sdist: 36 | name: Make SDist 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 0 # Optional, use if you use setuptools_scm 42 | submodules: true # Optional, use if you have submodules 43 | 44 | - name: Build SDist 45 | run: pipx run build --sdist 46 | 47 | - uses: actions/upload-artifact@v4 48 | with: 49 | name: cibw-sdist 50 | path: dist/*.tar.gz 51 | 52 | 53 | upload_all: 54 | needs: [build_wheels, make_sdist] 55 | runs-on: ubuntu-latest 56 | environment: release 57 | permissions: 58 | # IMPORTANT: this permission is mandatory for trusted publishing 59 | id-token: write 60 | # if: github.event_name == 'release' && github.event.action == 'published' 61 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 62 | steps: 63 | - uses: actions/download-artifact@v4 64 | with: 65 | pattern: cibw-* 66 | path: dist 67 | merge-multiple: true 68 | - uses: pypa/gh-action-pypi-publish@v1.12.4 69 | 70 | concurrency: 71 | group: ${{ github.workflow }}-${{ github.ref }} 72 | cancel-in-progress: true 73 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Codecov 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | check_coverage: 6 | name: Generate codecov 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - uses: actions/setup-python@v5 12 | name: Install Python 13 | 14 | - name: Generate coverage report 15 | run: | 16 | pip install pytest 17 | pip install pytest-cov 18 | pip install -e . 19 | pytest --cov=dtw --cov-report=xml 20 | 21 | - name: Upload coverage to Codecov 22 | uses: codecov/codecov-action@v5 23 | with: 24 | token: ${{ secrets.CODECOV_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | dtw/_dtw_utils.c 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | *~ 107 | .DS_Store 108 | 109 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Toni Giorgino 9 | 10 | Contributors 11 | ------------ 12 | 13 | Many. See GitHub. 14 | 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 5 | v1.5.0 (2024-05-23) 6 | ------------------- 7 | - Modernize build and attempt to numpy 2. [Toni] 8 | - Merge pull request #93 from 9 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.18.1. 10 | [Toni G] 11 | 12 | Bump pypa/cibuildwheel from 2.17.0 to 2.18.1 13 | - --- updated-dependencies: - dependency-name: pypa/cibuildwheel 14 | dependency-type: direct:production update-type: version- 15 | update:semver-minor ... [dependabot[bot]] 16 | - No inh diag. [Toni] 17 | 18 | 19 | v1.4.4 (2024-05-18) 20 | ------------------- 21 | - Update cython 3. [Toni] 22 | 23 | 24 | v1.4.3 (2024-05-18) 25 | ------------------- 26 | - Fix test for numpy2. [Toni] 27 | - List new Python versions. [Toni] 28 | 29 | 30 | v1.4.2 (2024-03-19) 31 | ------------------- 32 | - Changelog. [Toni] 33 | - Update build_wheels.yml. [Toni G] 34 | 35 | 36 | v1.4.1 (2024-03-18) 37 | ------------------- 38 | - Update build_wheels.yml. [Toni G] 39 | - Update build_wheels.yml. [Toni G] 40 | 41 | concurrency 42 | - Update build_wheels.yml. [Toni G] 43 | 44 | cancel-in-progress: true 45 | - Update build_wheels.yml. [Toni G] 46 | 47 | fix artifact name? 48 | - Update build_wheels.yml. [Toni G] 49 | - Merge pull request #88 from 50 | DynamicTimeWarping/dependabot/github_actions/pypa/gh-action-pypi- 51 | publish-1.8.14. [Toni G] 52 | 53 | Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.14 54 | - Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.14. 55 | [dependabot[bot]] 56 | 57 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.11 to 1.8.14. 58 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 59 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.11...v1.8.14) 60 | 61 | --- 62 | updated-dependencies: 63 | - dependency-name: pypa/gh-action-pypi-publish 64 | dependency-type: direct:production 65 | update-type: version-update:semver-patch 66 | ... 67 | - Merge pull request #89 from 68 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.17.0. 69 | [Toni G] 70 | 71 | Bump pypa/cibuildwheel from 2.16.5 to 2.17.0 72 | - Bump pypa/cibuildwheel from 2.16.5 to 2.17.0. [dependabot[bot]] 73 | 74 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.16.5 to 2.17.0. 75 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 76 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 77 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.16.5...v2.17.0) 78 | 79 | --- 80 | updated-dependencies: 81 | - dependency-name: pypa/cibuildwheel 82 | dependency-type: direct:production 83 | update-type: version-update:semver-minor 84 | ... 85 | - Delete .github/ISSUE_TEMPLATE.md. [Toni G] 86 | - Update issue templates. [Toni G] 87 | 88 | 89 | v1.4.0 (2024-03-18) 90 | ------------------- 91 | - Fix off-by-one in warp. [Toni] 92 | - Merge pull request #85 from 93 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.16.5. 94 | [Toni G] 95 | 96 | Bump pypa/cibuildwheel from 2.16.2 to 2.16.5 97 | - Bump pypa/cibuildwheel from 2.16.2 to 2.16.5. [dependabot[bot]] 98 | 99 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.16.2 to 2.16.5. 100 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 101 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 102 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.16.2...v2.16.5) 103 | 104 | --- 105 | updated-dependencies: 106 | - dependency-name: pypa/cibuildwheel 107 | dependency-type: direct:production 108 | update-type: version-update:semver-patch 109 | ... 110 | - Squashed commit of the following: [Toni] 111 | 112 | commit 7a3b4fd2689e0652b21d16469ae6406ba66125c3 113 | - Use trusted PyPI uploads. [Toni G] 114 | - Update build_wheels.yml. [Toni G] 115 | 116 | 117 | v1.3.1 (2023-12-20) 118 | ------------------- 119 | - Fix transpose. [Toni] 120 | - Merge pull request #80 from 121 | DynamicTimeWarping/dependabot/github_actions/actions/download- 122 | artifact-4. [Toni G] 123 | 124 | Bump actions/download-artifact from 3 to 4 125 | - Bump actions/download-artifact from 3 to 4. [dependabot[bot]] 126 | 127 | Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4. 128 | - [Release notes](https://github.com/actions/download-artifact/releases) 129 | - [Commits](https://github.com/actions/download-artifact/compare/v3...v4) 130 | 131 | --- 132 | updated-dependencies: 133 | - dependency-name: actions/download-artifact 134 | dependency-type: direct:production 135 | update-type: version-update:semver-major 136 | ... 137 | - Merge pull request #79 from 138 | DynamicTimeWarping/dependabot/github_actions/actions/setup-python-5. 139 | [Toni G] 140 | 141 | Bump actions/setup-python from 4 to 5 142 | - Bump actions/setup-python from 4 to 5. [dependabot[bot]] 143 | 144 | Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. 145 | - [Release notes](https://github.com/actions/setup-python/releases) 146 | - [Commits](https://github.com/actions/setup-python/compare/v4...v5) 147 | 148 | --- 149 | updated-dependencies: 150 | - dependency-name: actions/setup-python 151 | dependency-type: direct:production 152 | update-type: version-update:semver-major 153 | ... 154 | - Merge pull request #75 from 155 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.16.2. 156 | [Toni G] 157 | 158 | Bump pypa/cibuildwheel from 2.16.0 to 2.16.2 159 | - Bump pypa/cibuildwheel from 2.16.0 to 2.16.2. [dependabot[bot]] 160 | 161 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.16.0 to 2.16.2. 162 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 163 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 164 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.16.0...v2.16.2) 165 | 166 | --- 167 | updated-dependencies: 168 | - dependency-name: pypa/cibuildwheel 169 | dependency-type: direct:production 170 | update-type: version-update:semver-patch 171 | ... 172 | - Merge pull request #78 from 173 | DynamicTimeWarping/dependabot/github_actions/pypa/gh-action-pypi- 174 | publish-1.8.11. [Toni G] 175 | 176 | Bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 177 | - Bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11. 178 | [dependabot[bot]] 179 | 180 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.11. 181 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 182 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.10...v1.8.11) 183 | 184 | --- 185 | updated-dependencies: 186 | - dependency-name: pypa/gh-action-pypi-publish 187 | dependency-type: direct:production 188 | update-type: version-update:semver-patch 189 | ... 190 | - Merge pull request #69 from 191 | DynamicTimeWarping/dependabot/github_actions/pypa/gh-action-pypi- 192 | publish-1.8.10. [Toni G] 193 | 194 | Bump pypa/gh-action-pypi-publish from 1.8.6 to 1.8.10 195 | - Bump pypa/gh-action-pypi-publish from 1.8.6 to 1.8.10. 196 | [dependabot[bot]] 197 | 198 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.6 to 1.8.10. 199 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 200 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.6...v1.8.10) 201 | 202 | --- 203 | updated-dependencies: 204 | - dependency-name: pypa/gh-action-pypi-publish 205 | dependency-type: direct:production 206 | update-type: version-update:semver-patch 207 | ... 208 | - Merge pull request #70 from 209 | DynamicTimeWarping/dependabot/github_actions/actions/checkout-4. [Toni 210 | G] 211 | 212 | Bump actions/checkout from 3 to 4 213 | - Bump actions/checkout from 3 to 4. [dependabot[bot]] 214 | 215 | Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. 216 | - [Release notes](https://github.com/actions/checkout/releases) 217 | - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) 218 | - [Commits](https://github.com/actions/checkout/compare/v3...v4) 219 | 220 | --- 221 | updated-dependencies: 222 | - dependency-name: actions/checkout 223 | dependency-type: direct:production 224 | update-type: version-update:semver-major 225 | ... 226 | - Merge pull request #71 from 227 | DynamicTimeWarping/dependabot/github_actions/codecov/codecov-action-4. 228 | [Toni G] 229 | 230 | Bump codecov/codecov-action from 3 to 4 231 | - Bump codecov/codecov-action from 3 to 4. [dependabot[bot]] 232 | 233 | Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. 234 | - [Release notes](https://github.com/codecov/codecov-action/releases) 235 | - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) 236 | - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) 237 | 238 | --- 239 | updated-dependencies: 240 | - dependency-name: codecov/codecov-action 241 | dependency-type: direct:production 242 | update-type: version-update:semver-major 243 | ... 244 | - Merge pull request #73 from 245 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.16.0. 246 | [Toni G] 247 | 248 | Bump pypa/cibuildwheel from 2.13.1 to 2.16.0 249 | - Bump pypa/cibuildwheel from 2.13.1 to 2.16.0. [dependabot[bot]] 250 | 251 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.13.1 to 2.16.0. 252 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 253 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 254 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.13.1...v2.16.0) 255 | 256 | --- 257 | updated-dependencies: 258 | - dependency-name: pypa/cibuildwheel 259 | dependency-type: direct:production 260 | update-type: version-update:semver-minor 261 | ... 262 | - Merge pull request #61 from 263 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.13.1. 264 | [Toni G] 265 | 266 | Bump pypa/cibuildwheel from 2.12.3 to 2.13.1 267 | - Bump pypa/cibuildwheel from 2.12.3 to 2.13.1. [dependabot[bot]] 268 | 269 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.12.3 to 2.13.1. 270 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 271 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 272 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.12.3...v2.13.1) 273 | 274 | --- 275 | updated-dependencies: 276 | - dependency-name: pypa/cibuildwheel 277 | dependency-type: direct:production 278 | update-type: version-update:semver-minor 279 | ... 280 | - Merge pull request #58 from 281 | DynamicTimeWarping/dependabot/github_actions/pypa/gh-action-pypi- 282 | publish-1.8.6. [Toni G] 283 | 284 | Bump pypa/gh-action-pypi-publish from 1.6.4 to 1.8.6 285 | - Bump pypa/gh-action-pypi-publish from 1.6.4 to 1.8.6. 286 | [dependabot[bot]] 287 | 288 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.6.4 to 1.8.6. 289 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 290 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.6.4...v1.8.6) 291 | 292 | --- 293 | updated-dependencies: 294 | - dependency-name: pypa/gh-action-pypi-publish 295 | dependency-type: direct:production 296 | update-type: version-update:semver-minor 297 | ... 298 | - Merge pull request #57 from 299 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.12.3. 300 | [Toni G] 301 | 302 | Bump pypa/cibuildwheel from 2.12.0 to 2.12.3 303 | - Bump pypa/cibuildwheel from 2.12.0 to 2.12.3. [dependabot[bot]] 304 | 305 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.12.0 to 2.12.3. 306 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 307 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 308 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.12.0...v2.12.3) 309 | 310 | --- 311 | updated-dependencies: 312 | - dependency-name: pypa/cibuildwheel 313 | dependency-type: direct:production 314 | update-type: version-update:semver-patch 315 | ... 316 | - Merge pull request #47 from 317 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.12.0. 318 | [Toni G] 319 | 320 | Bump pypa/cibuildwheel from 2.11.3 to 2.12.0 321 | - Bump pypa/cibuildwheel from 2.11.3 to 2.12.0. [dependabot[bot]] 322 | 323 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.11.3 to 2.12.0. 324 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 325 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 326 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.11.3...v2.12.0) 327 | 328 | --- 329 | updated-dependencies: 330 | - dependency-name: pypa/cibuildwheel 331 | dependency-type: direct:production 332 | update-type: version-update:semver-minor 333 | ... 334 | - Merge pull request #45 from 335 | DynamicTimeWarping/dependabot/github_actions/pypa/gh-action-pypi- 336 | publish-1.6.4. [Toni G] 337 | 338 | Bump pypa/gh-action-pypi-publish from 1.6.1 to 1.6.4 339 | - Bump pypa/gh-action-pypi-publish from 1.6.1 to 1.6.4. 340 | [dependabot[bot]] 341 | 342 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.6.1 to 1.6.4. 343 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 344 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.6.1...v1.6.4) 345 | 346 | --- 347 | updated-dependencies: 348 | - dependency-name: pypa/gh-action-pypi-publish 349 | dependency-type: direct:production 350 | update-type: version-update:semver-patch 351 | ... 352 | - Merge pull request #43 from 353 | DynamicTimeWarping/dependabot/github_actions/pypa/gh-action-pypi- 354 | publish-1.6.1. [Toni G] 355 | 356 | Bump pypa/gh-action-pypi-publish from 1.5.1 to 1.6.1 357 | - Bump pypa/gh-action-pypi-publish from 1.5.1 to 1.6.1. 358 | [dependabot[bot]] 359 | 360 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.5.1 to 1.6.1. 361 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 362 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.5.1...v1.6.1) 363 | 364 | --- 365 | updated-dependencies: 366 | - dependency-name: pypa/gh-action-pypi-publish 367 | dependency-type: direct:production 368 | update-type: version-update:semver-minor 369 | ... 370 | - Merge pull request #44 from 371 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.11.3. 372 | [Toni G] 373 | 374 | Bump pypa/cibuildwheel from 2.11.2 to 2.11.3 375 | - Bump pypa/cibuildwheel from 2.11.2 to 2.11.3. [dependabot[bot]] 376 | 377 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.11.2 to 2.11.3. 378 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 379 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 380 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.11.2...v2.11.3) 381 | 382 | --- 383 | updated-dependencies: 384 | - dependency-name: pypa/cibuildwheel 385 | dependency-type: direct:production 386 | update-type: version-update:semver-patch 387 | ... 388 | - Merge pull request #41 from 389 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.11.2. 390 | [Toni G] 391 | 392 | Bump pypa/cibuildwheel from 2.11.1 to 2.11.2 393 | - Bump pypa/cibuildwheel from 2.11.1 to 2.11.2. [dependabot[bot]] 394 | 395 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.11.1 to 2.11.2. 396 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 397 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 398 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.11.1...v2.11.2) 399 | 400 | --- 401 | updated-dependencies: 402 | - dependency-name: pypa/cibuildwheel 403 | dependency-type: direct:production 404 | update-type: version-update:semver-patch 405 | ... 406 | - Merge pull request #40 from 407 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.11.1. 408 | [Toni G] 409 | 410 | Bump pypa/cibuildwheel from 2.10.2 to 2.11.1 411 | - Bump pypa/cibuildwheel from 2.10.2 to 2.11.1. [dependabot[bot]] 412 | 413 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.10.2 to 2.11.1. 414 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 415 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 416 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.10.2...v2.11.1) 417 | 418 | --- 419 | updated-dependencies: 420 | - dependency-name: pypa/cibuildwheel 421 | dependency-type: direct:production 422 | update-type: version-update:semver-minor 423 | ... 424 | - Update issue templates. [Toni G] 425 | - Merge pull request #37 from 426 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.10.2. 427 | [Toni G] 428 | 429 | Bump pypa/cibuildwheel from 2.10.1 to 2.10.2 430 | - Bump pypa/cibuildwheel from 2.10.1 to 2.10.2. [dependabot[bot]] 431 | 432 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.10.1 to 2.10.2. 433 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 434 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 435 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.10.1...v2.10.2) 436 | 437 | --- 438 | updated-dependencies: 439 | - dependency-name: pypa/cibuildwheel 440 | dependency-type: direct:production 441 | update-type: version-update:semver-patch 442 | ... 443 | - Merge pull request #35 from 444 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.10.1. 445 | [Toni G] 446 | 447 | Bump pypa/cibuildwheel from 2.10.0 to 2.10.1 448 | - Bump pypa/cibuildwheel from 2.10.0 to 2.10.1. [dependabot[bot]] 449 | 450 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.10.0 to 2.10.1. 451 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 452 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 453 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.10.0...v2.10.1) 454 | 455 | --- 456 | updated-dependencies: 457 | - dependency-name: pypa/cibuildwheel 458 | dependency-type: direct:production 459 | update-type: version-update:semver-patch 460 | ... 461 | - Merge pull request #34 from 462 | DynamicTimeWarping/dependabot/github_actions/pypa/cibuildwheel-2.10.0. 463 | [Toni G] 464 | 465 | Bump pypa/cibuildwheel from 2.9.0 to 2.10.0 466 | - Bump pypa/cibuildwheel from 2.9.0 to 2.10.0. [dependabot[bot]] 467 | 468 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.9.0 to 2.10.0. 469 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 470 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 471 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.9.0...v2.10.0) 472 | 473 | --- 474 | updated-dependencies: 475 | - dependency-name: pypa/cibuildwheel 476 | dependency-type: direct:production 477 | update-type: version-update:semver-minor 478 | ... 479 | 480 | 481 | v1.3.0 (2022-09-02) 482 | ------------------- 483 | - Raise error if unknown plot type. [Toni] 484 | - Coverage. [Toni] 485 | - Do not call plt.show() [Toni] 486 | - Ignore backup files. [Toni] 487 | 488 | 489 | v1.2.3 (2022-09-02) 490 | ------------------- 491 | - Last before bumpversion. [Toni] 492 | - Plot two way now converts xts to numpy. [Toni] 493 | - Fix https://github.com/DynamicTimeWarping/dtw-python/issues/33. [Toni] 494 | - Apidocs reqs. [Toni] 495 | - Different detection whether running interactively. [Toni G] 496 | - Delete .travis.yml. [Toni G] 497 | - Bump pypa/cibuildwheel from 2.8.1 to 2.9.0 (#30) [Toni G, 498 | dependabot[bot], dependabot[bot]] 499 | 500 | * Bump pypa/cibuildwheel from 2.8.1 to 2.9.0 501 | 502 | Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.8.1 to 2.9.0. 503 | - [Release notes](https://github.com/pypa/cibuildwheel/releases) 504 | - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) 505 | - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.8.1...v2.9.0) 506 | 507 | --- 508 | updated-dependencies: 509 | - dependency-name: pypa/cibuildwheel 510 | dependency-type: direct:production 511 | update-type: version-update:semver-minor 512 | ... 513 | 514 | 515 | v1.2.2 (2022-07-27) 516 | ------------------- 517 | - Bump pypa/gh-action-pypi-publish from 1.5.0 to 1.5.1 (#25) 518 | [dependabot[bot]] 519 | 520 | Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.5.0 to 1.5.1. 521 | - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) 522 | - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.5.0...v1.5.1) 523 | 524 | --- 525 | updated-dependencies: 526 | - dependency-name: pypa/gh-action-pypi-publish 527 | dependency-type: direct:production 528 | update-type: version-update:semver-patch 529 | ... 530 | - Bump actions/checkout from 2 to 3 (#26) [dependabot[bot]] 531 | 532 | Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. 533 | - [Release notes](https://github.com/actions/checkout/releases) 534 | - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) 535 | - [Commits](https://github.com/actions/checkout/compare/v2...v3) 536 | 537 | --- 538 | updated-dependencies: 539 | - dependency-name: actions/checkout 540 | dependency-type: direct:production 541 | update-type: version-update:semver-major 542 | ... 543 | - Bump actions/setup-python from 2 to 4 (#27) [dependabot[bot]] 544 | 545 | Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 4. 546 | - [Release notes](https://github.com/actions/setup-python/releases) 547 | - [Commits](https://github.com/actions/setup-python/compare/v2...v4) 548 | 549 | --- 550 | updated-dependencies: 551 | - dependency-name: actions/setup-python 552 | dependency-type: direct:production 553 | update-type: version-update:semver-major 554 | ... 555 | - Bump codecov/codecov-action from 2 to 3 (#28) [dependabot[bot]] 556 | 557 | Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2 to 3. 558 | - [Release notes](https://github.com/codecov/codecov-action/releases) 559 | - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) 560 | - [Commits](https://github.com/codecov/codecov-action/compare/v2...v3) 561 | 562 | --- 563 | updated-dependencies: 564 | - dependency-name: codecov/codecov-action 565 | dependency-type: direct:production 566 | update-type: version-update:semver-major 567 | ... 568 | - Create dependabot.yml. [Toni G] 569 | - Update build_wheels.yml. [Toni G] 570 | - Update build_wheels.yml. [Toni G] 571 | - Update build_wheels.yml. [Toni G] 572 | 573 | 574 | v1.2.0 (2022-07-25) 575 | ------------------- 576 | - Changelog. [Toni] 577 | 578 | 579 | v1.1.15 (2022-07-25) 580 | -------------------- 581 | - Fix license. [Toni] 582 | - Fix license files. [Toni] 583 | - Reference time series is now plotted on the same (#23) [Nicholas 584 | Livingstone, Nicholas Livingstone] 585 | 586 | axis as the query. In the event of a non-zero offset, the reference 587 | values are adjusted by the offset and an offset twin axis is generated with the 588 | same scale of the original axis. 589 | - Update build_wheels.yml. [Toni G] 590 | 591 | 592 | v1.1.14 (2022-06-17) 593 | -------------------- 594 | - Last before bumpversion. [Toni] 595 | - Push tags. [Toni] 596 | - Disable warparea test. [Toni] 597 | - Disable warparea test. [Toni] 598 | - Disable warparea test. [Toni] 599 | 600 | 601 | v1.1.13 (2022-06-17) 602 | -------------------- 603 | - Last before bumpversion. [Toni] 604 | - Update warpArea.py. [Toni G] 605 | - Cibuildwheel2 (#19) [Toni G] 606 | 607 | * test updated ci 608 | 609 | * new 610 | 611 | * update codecov action 612 | 613 | * archs 614 | 615 | * auto64 616 | 617 | * skip musl 618 | - Added axes labels for dtwPlotTwoWay (#15) [Boje Deforce] 619 | 620 | Axes labels were not being added in the dtwPlotTwoWay function. This is now added. 621 | 622 | 623 | v1.1.12 (2022-01-14) 624 | -------------------- 625 | - Last before bumpversion. [Toni] 626 | - Fix license. [Toni] 627 | 628 | 629 | v1.1.11 (2022-01-14) 630 | -------------------- 631 | - Last before bumpversion. [Toni] 632 | - Vectorized window functions. [Toni] 633 | 634 | 635 | v1.1.10 (2021-04-10) 636 | -------------------- 637 | - Last before bumpversion. [Toni] 638 | - Maint. [Toni] 639 | - Maint. [Toni] 640 | - Maintenance. [Toni] 641 | - Build maintenance. [Toni] 642 | 643 | 644 | v1.1.9 (2021-04-10) 645 | ------------------- 646 | - Last before bumpversion. [Toni] 647 | - Made cython necessary for building. [Toni] 648 | - Revert misguided Makefile. [Toni] 649 | 650 | 651 | v1.1.8 (2021-04-10) 652 | ------------------- 653 | - Changelog. [Toni] 654 | - Last before bumpversion. [Toni] 655 | - Update cython. fix np.int warning. [Toni] 656 | - Update setup.py. [Toni G] 657 | - Raise numpy requirements. [Toni G] 658 | 659 | 660 | v1.1.7 (2021-03-24) 661 | ------------------- 662 | - Last before bumpversion. [Toni] 663 | - Last before bumpversion. [Toni] 664 | - Add universal binaries. [Toni G] 665 | - Update cibuildwheel (#10) [Toni G] 666 | 667 | * Update build.yml 668 | 669 | * Update build.yml 670 | - Roxygen works again, revamped docs. [Toni] 671 | - New maint code. [Toni] 672 | - Note on abandoning autogen. [Toni] 673 | - Merge from R. [Toni] 674 | 675 | 676 | v1.1.6 (2020-09-25) 677 | ------------------- 678 | - Last before bumpversion. [Toni] 679 | - Merged with R. [Toni] 680 | 681 | 682 | v1.1.5 (2020-06-22) 683 | ------------------- 684 | - Remove runtime dep on setuptools. [Toni] 685 | - Badges. [Toni] 686 | - Codecov. [Toni] 687 | - Codecov. [Toni] 688 | - Codecov. [Toni] 689 | - Codecov. [Toni] 690 | - Changelog. [Toni] 691 | 692 | 693 | v1.1.4 (2020-06-19) 694 | ------------------- 695 | - Add callable main. cleanups. [Toni] 696 | 697 | 698 | v1.1.3 (2020-06-18) 699 | ------------------- 700 | - Doctests again. [Toni] 701 | 702 | 703 | v1.1.2 (2020-06-18) 704 | ------------------- 705 | - Ci doctests. [Toni] 706 | 707 | 708 | v1.1.1 (2020-06-18) 709 | ------------------- 710 | - Doctests. [Toni] 711 | - Improve warp and docs. [Toni] 712 | 713 | 714 | v1.1.0 (2020-06-18) 715 | ------------------- 716 | - Before bump. [Toni] 717 | - Dont cythonize in build. [Toni] 718 | - Test deployer. [Toni] 719 | - Python version. [Toni] 720 | - Absolute imports. [Toni] 721 | 722 | abs imports 723 | - Test setup requires. [Toni] 724 | - Test cibuildwheel. [Toni] 725 | - Added test for issue https://github.com/DynamicTimeWarping/dtw- 726 | python/issues/5. [Toni] 727 | 728 | 729 | v1.0.6 (2020-06-17) 730 | ------------------- 731 | - Fixed subtle bug with open_begin. [Toni] 732 | - Adding CRAN test equivalent. [Toni] 733 | - Fixes https://github.com/DynamicTimeWarping/dtw-python/issues/5. 734 | [Toni] 735 | 736 | 737 | v1.0.5 (2020-02-24) 738 | ------------------- 739 | - Fix for open-end with slope-constrained alignments. [Toni] 740 | - Merge pull request #3 from tcwalther/fix-slantedbandwindow. [Toni G] 741 | 742 | fix slantedBandWindow function - thanks! 743 | - Fix slantedBandWindow function. [Thomas Walther] 744 | 745 | The previous version had variable names such as query.size in it 746 | - notation which is common in R, but doesn't work in Python. 747 | This commit corrects these names to query_size, reference_size 748 | and window_size, respectively. 749 | 750 | 751 | v1.0.4 (2019-12-30) 752 | ------------------- 753 | - Fixes https://github.com/DynamicTimeWarping/dtw-python/issues/1. 754 | [Toni] 755 | 756 | 757 | v1.0.3 (2019-10-31) 758 | ------------------- 759 | - Possible fix for windows. [Toni] 760 | - Conda env. [Toni] 761 | 762 | 763 | v1.0.2 (2019-09-04) 764 | ------------------- 765 | - Minor fixes. [Toni] 766 | - Aami doctests maybe. [Toni] 767 | 768 | 769 | v1.0.1 (2019-09-01) 770 | ------------------- 771 | - Last before bumpversion. [Toni] 772 | - Misc bugs. [Toni] 773 | 774 | 775 | v0.5.2 (2019-08-31) 776 | ------------------- 777 | - Last before bumpversion. [Toni] 778 | 779 | 780 | v0.5.1 (2019-08-31) 781 | ------------------- 782 | - Last before bumpversion. [Toni] 783 | 784 | 785 | v0.5.0 (2019-08-31) 786 | ------------------- 787 | - Docs index. [Toni] 788 | - Fixup docs. [Toni] 789 | - Automodapi. [Toni] 790 | - Using bootstrap theme. [Toni] 791 | 792 | 793 | v0.4.0 (2019-08-30) 794 | ------------------- 795 | - Renames. [Toni] 796 | - Rename. [Toni] 797 | 798 | 799 | v0.3.10 (2019-08-30) 800 | -------------------- 801 | - Make release. [Toni] 802 | 803 | 804 | v0.3.9 (2019-08-30) 805 | ------------------- 806 | - Make release. [Toni] 807 | 808 | 809 | v0.3.7 (2019-08-30) 810 | ------------------- 811 | - Setup. [Toni] 812 | 813 | 814 | v0.3.6 (2019-08-30) 815 | ------------------- 816 | - Return axes plot. [Toni] 817 | 818 | 819 | v0.3.0 (2019-08-29) 820 | ------------------- 821 | - C file. [Toni] 822 | 823 | 824 | v0.2.0 (2019-08-29) 825 | ------------------- 826 | - Doctests. [Toni] 827 | - Dtw doc. [Toni] 828 | - Squash spaces. [Toni] 829 | - Tests. [Toni] 830 | - Rename. [Toni] 831 | - Progress. [Toni] 832 | - Going for tests. [Toni] 833 | - Checkpoint. [Toni] 834 | - Testing examples. [Toni] 835 | - New docs. [Toni] 836 | - Hide _get_p. [Toni] 837 | - Entry point. [Toni] 838 | - Remove dependency on click. [Toni] 839 | - Tests. [Toni] 840 | - Test cli. [Toni] 841 | - Cli ok. [Toni] 842 | - Attempt at reformat. [Toni] 843 | 844 | 845 | v0.1.1 (2019-08-27) 846 | ------------------- 847 | - Underscore. [Toni] 848 | - Density. [Toni] 849 | - W2 works. [Toni] 850 | - Lambda error. [Toni] 851 | - Plot2. [Toni] 852 | - Plots. [Toni] 853 | - Step pattern by name. [Toni] 854 | - Renamed internal modules. [Toni] 855 | - Renaming. [Toni] 856 | - Renaming. [Toni] 857 | - Renaming package. [Toni] 858 | - Markdown conversion. [Toni] 859 | - Progress. [Toni] 860 | - Docs in. [Toni] 861 | - Added tags. [Toni] 862 | - Inserting ok. [Toni] 863 | - Checkpoint. [Toni] 864 | - Abort. [Toni] 865 | - Abort roxypick. [Toni] 866 | - Headers. [Toni] 867 | - Plot step pattern. [Toni] 868 | - Countpaths. [Toni] 869 | - Countpaths. [Toni] 870 | - Mkdirdeltas. [Toni] 871 | - Windowing. [Toni] 872 | - Mvm, bt. [Toni] 873 | - Object. [Toni] 874 | - Progress. [Toni] 875 | - All patterns ok. [Toni] 876 | - Checkpoint. [Toni] 877 | - Pattern building. [Toni] 878 | - Listed step patterns. [Toni] 879 | - Print and T. [Toni] 880 | - Test ok. [Toni] 881 | - Builds. [Toni] 882 | - Checkpoint. [Toni] 883 | - More import. [Toni] 884 | - Initial import. [Toni] 885 | - Initial commit. [Toni G] 886 | 887 | 888 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | First of all, note that this code aims at feature-parity with R's 8 | dtw package. This includes documentation. 9 | 10 | The current codes focus on exhaustive implementation of the 11 | "normal" DTW algorithm. What algorithm is in and out is an 12 | admittedly opinionated choice. New features will be 13 | accepted in exceptional cases, as long as R-Python feature-parity 14 | is preserved. 15 | 16 | Contributions are welcome, and they are greatly appreciated! Every little bit 17 | helps, and credit will always be given. 18 | 19 | You can contribute in many ways: 20 | 21 | Types of Contributions 22 | ---------------------- 23 | 24 | Report Bugs 25 | ~~~~~~~~~~~ 26 | 27 | Report bugs at https://github.com/tonigi/dtw/issues. 28 | 29 | If you are reporting a bug, please include: 30 | 31 | * Your operating system name and version. 32 | * Any details about your local setup that might be helpful in troubleshooting. 33 | * Detailed steps to reproduce the bug. 34 | 35 | Fix Bugs 36 | ~~~~~~~~ 37 | 38 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 39 | wanted" is open to whoever wants to implement it. 40 | 41 | Implement Features 42 | ~~~~~~~~~~~~~~~~~~ 43 | 44 | Look through the GitHub issues for features. Anything tagged with "enhancement" 45 | and "help wanted" is open to whoever wants to implement it. 46 | 47 | Write Documentation 48 | ~~~~~~~~~~~~~~~~~~~ 49 | 50 | Python port of R's Comprehensive Dynamic Time Warp algorithm package could always use more documentation, whether as part of the 51 | official Python port of R's Comprehensive Dynamic Time Warp algorithm package docs, in docstrings, or even on the web in blog posts, 52 | articles, and such. 53 | 54 | Submit Feedback 55 | ~~~~~~~~~~~~~~~ 56 | 57 | The best way to send feedback is to file an issue at https://github.com/tonigi/dtw/issues. 58 | 59 | If you are proposing a feature: 60 | 61 | * Explain in detail how it would work. 62 | * Keep the scope as narrow as possible, to make it easier to implement. 63 | * Remember that this is a volunteer-driven project, and that contributions 64 | are welcome :) 65 | 66 | Get Started! 67 | ------------ 68 | 69 | Ready to contribute? Here's how to set up `dtw` for local development. 70 | 71 | 1. Fork the `dtw` repo on GitHub. 72 | 2. Clone your fork locally:: 73 | 74 | $ git clone git@github.com:your_name_here/dtw.git 75 | 76 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 77 | 78 | $ mkvirtualenv dtw 79 | $ cd dtw/ 80 | $ python setup.py develop 81 | 82 | 4. Create a branch for local development:: 83 | 84 | $ git checkout -b name-of-your-bugfix-or-feature 85 | 86 | Now you can make your changes locally. 87 | 88 | 5. When you're done making changes, check that your changes pass flake8 and the 89 | tests, including testing other Python versions with tox:: 90 | 91 | $ flake8 dtw tests 92 | $ python setup.py test or py.test 93 | $ tox 94 | 95 | To get flake8 and tox, just pip install them into your virtualenv. 96 | 97 | 6. Commit your changes and push your branch to GitHub:: 98 | 99 | $ git add . 100 | $ git commit -m "Your detailed description of your changes." 101 | $ git push origin name-of-your-bugfix-or-feature 102 | 103 | 7. Submit a pull request through the GitHub website. 104 | 105 | Pull Request Guidelines 106 | ----------------------- 107 | 108 | Before you submit a pull request, check that it meets these guidelines: 109 | 110 | 1. The pull request should include tests. 111 | 2. If the pull request adds functionality, the docs should be updated. Put 112 | your new functionality into a function with a docstring, and add the 113 | feature to the list in README.rst. 114 | 3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check 115 | https://travis-ci.org/tonigi/dtw/pull_requests 116 | and make sure that the tests pass for all supported Python versions. 117 | 118 | Tips 119 | ---- 120 | 121 | To run a subset of tests:: 122 | 123 | 124 | $ python -m unittest tests.test_dtw 125 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | include *.md 3 | include COPYING 4 | 5 | recursive-include tests * 6 | recursive-exclude * __pycache__ 7 | recursive-exclude * *.py[co] 8 | 9 | # recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 10 | 11 | global-include dtw/*.c 12 | global-include dtw/*.h 13 | global-include dtw/*.pyx 14 | 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | ruff check dtw tests 55 | 56 | test: ## run tests quickly with the default Python 57 | pytest 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source dtw setup.py test 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | # rm -f docs/dtw.rst 70 | # rm -f docs/modules.rst 71 | # sphinx-apidoc -o docs/ dtw 72 | rm -rf docs/api 73 | $(MAKE) -C docs clean 74 | $(MAKE) -C docs html 75 | $(BROWSER) docs/_build/html/index.html 76 | 77 | servedocs: docs ## compile the docs watching for changes 78 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 79 | 80 | release: dist ## package and upload a release 81 | twine upload dist/* 82 | 83 | dist: clean ## builds source and wheel package 84 | python setup.py sdist 85 | python setup.py bdist_wheel 86 | ls -l dist 87 | 88 | install: clean ## install the package to the active Python's site-packages 89 | python setup.py install 90 | 91 | 92 | docstrings: 93 | python maintainer/roxypick.py 94 | 95 | bump: changelog 96 | git commit -a -m "last before bumpversion" 97 | bumpversion patch 98 | git push --tags 99 | git push 100 | 101 | changelog: 102 | -gitchangelog > CHANGELOG.md 103 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Welcome to the dtw-python package 2 | ================================= 3 | 4 | Comprehensive implementation of `Dynamic Time Warping algorithms 5 | `__. 6 | 7 | DTW is a family of algorithms which compute the local stretch or 8 | compression to apply to the time axes of two timeseries in order to 9 | optimally map one (query) onto the other (reference). DTW outputs the 10 | remaining cumulative distance between the two and, if desired, the 11 | mapping itself (warping function). DTW is widely used e.g. for 12 | classification and clustering tasks in econometrics, chemometrics and 13 | general timeseries mining. 14 | 15 | This package provides the most complete, freely-available (GPL) 16 | implementation of Dynamic Time Warping-type (DTW) algorithms up to 17 | date. It is a faithful Python equivalent of `R's DTW package on CRAN 18 | `__. Supports arbitrary local (e.g. 19 | symmetric, asymmetric, slope-limited) and global (windowing) 20 | constraints, fast native code, several plot styles, and more. 21 | 22 | 23 | .. image:: https://github.com/DynamicTimeWarping/dtw-python/workflows/Build%20and%20upload%20to%20PyPI/badge.svg 24 | :target: https://github.com/DynamicTimeWarping/dtw-python/actions 25 | .. image:: https://badge.fury.io/py/dtw-python.svg 26 | :target: https://badge.fury.io/py/dtw-python 27 | .. image:: https://codecov.io/gh/DynamicTimeWarping/dtw-python/branch/master/graph/badge.svg 28 | :target: https://codecov.io/gh/DynamicTimeWarping/dtw-python 29 | 30 | 31 | 32 | Documentation 33 | ~~~~~~~~~~~~~ 34 | 35 | Please refer to the main `DTW suite homepage 36 | `__ for the full documentation 37 | and background. 38 | 39 | The best place to learn how to use the package (and a hopefully a 40 | decent deal of background on DTW) is the companion paper `Computing 41 | and Visualizing Dynamic Time Warping Alignments in R: The dtw Package 42 | `__, which the Journal of 43 | Statistical Software makes available for free. It includes detailed 44 | instructions and extensive background on things like multivariate 45 | matching, open-end variants for real-time use, interplay between 46 | recursion types and length normalization, history, etc. 47 | 48 | To have a look at how the *dtw* package is used in domains ranging from 49 | bioinformatics to chemistry to data mining, have a look at the list of 50 | `citing 51 | papers `__. 52 | 53 | **Note**: **R** is the prime environment for the DTW 54 | suite. Python's docstrings and the API below are generated 55 | automatically for the sake of consistency and maintainability, and may 56 | not be as pretty. 57 | 58 | 59 | Features 60 | ~~~~~~~~ 61 | 62 | The implementation provides: 63 | 64 | - arbitrary windowing functions (global constraints), eg. the 65 | `Sakoe-Chiba 66 | band `__ 67 | and the `Itakura 68 | parallelogram `__; 69 | - arbitrary transition types (also known as step patterns, slope 70 | constraints, local constraints, or DP-recursion rules). This includes 71 | dozens of well-known types: 72 | 73 | - all step patterns classified by 74 | `Rabiner-Juang `__, 75 | `Sakoe-Chiba `__, 76 | and `Rabiner-Myers `__; 77 | - symmetric and asymmetric; 78 | - Rabiner's smoothed variants; 79 | - arbitrary, user-defined slope constraints 80 | 81 | - partial matches: open-begin, open-end, substring matches 82 | - proper, pattern-dependent, normalization (exact average distance per 83 | step) 84 | - the Minimum Variance Matching (MVM) algorithm `(Latecki et 85 | al.) `__ 86 | 87 | In addition to computing alignments, the package provides: 88 | 89 | - methods for plotting alignments and warping functions in several 90 | classic styles (see plot gallery); 91 | - graphical representation of step patterns; 92 | - functions for applying a warping function, either direct or inverse; 93 | - a fast native (C) core. 94 | 95 | 96 | Multivariate timeseries can be aligned with arbitrary local distance 97 | definitions, leveraging the [`proxy::dist`](https://www.rdocumentation.org/packages/proxy/versions/0.4-23/topics/dist) (R) or 98 | [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) (Python) functions. 99 | 100 | 101 | Citation 102 | ~~~~~~~~ 103 | 104 | When using in academic works please cite: 105 | 106 | * T. Giorgino. Computing and Visualizing Dynamic Time Warping Alignments in R: The dtw Package. J. Stat. Soft., 31 (2009) `doi:10.18637/jss.v031.i07 `__. 107 | 108 | When using partial matching (unconstrained endpoints via the open.begin/open.end options) and/or normalization strategies, please also cite: 109 | 110 | * P. Tormene, T. Giorgino, S. Quaglini, M. Stefanelli (2008). Matching Incomplete Time Series with Dynamic Time Warping: An Algorithm and an Application to Post-Stroke Rehabilitation. Artificial Intelligence in Medicine, 45(1), 11-34. `doi:10.1016/j.artmed.2008.11.007 `__ 111 | 112 | 113 | 114 | Source code 115 | ~~~~~~~~~~~ 116 | 117 | Releases (stable versions) are available in the `dtw-python project on 118 | PyPi `__. Development 119 | occurs on GitHub at . 120 | 121 | 122 | License 123 | ~~~~~~~ 124 | 125 | This program is free software: you can redistribute it and/or modify 126 | it under the terms of the GNU General Public License as published by 127 | the Free Software Foundation, either version 3 of the License, or 128 | (at your option) any later version. 129 | 130 | This program is distributed in the hope that it will be useful, 131 | but WITHOUT ANY WARRANTY; without even the implied warranty of 132 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 133 | GNU General Public License for more details. 134 | 135 | You should have received a copy of the GNU General Public License 136 | along with this program. If not, see . 137 | 138 | 139 | 140 | 141 | Credits 142 | ------- 143 | 144 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 145 | 146 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 147 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 148 | -------------------------------------------------------------------------------- /docs/CASE.SENSITIVE.FS: -------------------------------------------------------------------------------- 1 | Common subdirectories: ./__pycache__ and /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi/__pycache__ 2 | Only in /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi: automodapi.py~ 3 | diff -u ./automodsumm.py /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi/automodsumm.py 4 | --- ./automodsumm.py 2019-09-05 09:34:24.000000000 +0200 5 | +++ /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi/automodsumm.py 2019-08-31 09:35:17.000000000 +0200 6 | @@ -492,6 +492,8 @@ 7 | 8 | new_files.append(fn) 9 | 10 | + # fn = re.sub(r'([A-Z])',r'\1_',fn) # DTW 11 | + fn = fn.replace("DTW","D_T_W_") 12 | f = open(fn, 'w') 13 | 14 | try: 15 | Only in /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi: automodsumm.py~ 16 | Common subdirectories: ./templates and /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi/templates 17 | Common subdirectories: ./tests and /Users/toni/anaconda3-backup/lib/python3.6/site-packages/sphinx_automodapi/tests 18 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = dtw 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/apidocs-requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-automodapi 3 | sphinx-bootstrap-theme 4 | numpy 5 | scipy 6 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # dtw documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | # https://github.com/ryan-roemer/sphinx-bootstrap-theme 26 | import sphinx_bootstrap_theme 27 | 28 | html_theme_options = { 29 | # Navigation bar title. (Default: ``project`` value) 30 | 'navbar_title': "dtw-python", 31 | 32 | # Tab name for entire site. (Default: "Site") 33 | 'navbar_site_name': "Docs", 34 | 35 | # A list of tuples containing pages or urls to link to. 36 | # Valid tuples should be in the following forms: 37 | # (name, page) # a link to a page 38 | # (name, "/aa/bb", 1) # a link to an arbitrary relative url 39 | # (name, "http://example.com", True) # arbitrary absolute url 40 | # Note the "1" or "True" value above as the third argument to indicate 41 | # an arbitrary url. 42 | 'navbar_links': [ 43 | ("🏠 The DTW suite", "https://dynamictimewarping.github.io", True), 44 | ], 45 | 46 | # Render the next and previous page links in navbar. (Default: true) 47 | 'navbar_sidebarrel': False, 48 | 49 | # Render the current pages TOC in the navbar. (Default: true) 50 | # 'navbar_pagenav': True, 51 | 52 | # Tab name for the current pages TOC. (Default: "Page") 53 | 'navbar_pagenav_name': "Page", 54 | 55 | # Global TOC depth for "site" navbar tab. (Default: 1) 56 | # Switching to -1 shows all levels. 57 | 'globaltoc_depth': -1, 58 | 59 | # Include hidden TOCs in Site navbar? 60 | # 61 | # Note: If this is "false", you cannot have mixed ``:hidden:`` and 62 | # non-hidden ``toctree`` directives in the same page, or else the build 63 | # will break. 64 | # 65 | # Values: "true" (default) or "false" 66 | # 'globaltoc_includehidden': "true", 67 | 68 | # HTML navbar class (Default: "navbar") to attach to
element. 69 | # For black navbar, do "navbar navbar-inverse" 70 | # 'navbar_class': "navbar navbar-inverse", 71 | 72 | # Fix navigation bar to top of page? 73 | # Values: "true" (default) or "false" 74 | # 'navbar_fixed_top': "true", 75 | 76 | # Location of link to source. 77 | # Options are "nav" (default), "footer" or anything else to exclude. 78 | 'source_link_position': "none", 79 | 80 | # Bootswatch (http://bootswatch.com/) theme. 81 | # 82 | # Options are nothing (default) or the name of a valid theme 83 | # such as "cosmo" or "sandstone". 84 | # 85 | # The set of valid themes depend on the version of Bootstrap 86 | # that's used (the next config option). 87 | # 88 | # Currently, the supported themes are: 89 | # - Bootstrap 2: https://bootswatch.com/2 90 | # - Bootstrap 3: https://bootswatch.com/3 91 | 'bootswatch_theme': "yeti", 92 | 93 | # Choose Bootstrap version. 94 | # Values: "3" (default) or "2" (in quotes) 95 | 'bootstrap_version': "3", 96 | } 97 | 98 | 99 | import dtw 100 | 101 | # -- General configuration --------------------------------------------- 102 | 103 | # If your documentation needs a minimal Sphinx version, state it here. 104 | # 105 | # needs_sphinx = '1.0' 106 | 107 | mytitle = u"The dtw-python package" 108 | 109 | # Add any Sphinx extension module names here, as strings. They can be 110 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 111 | extensions = ['sphinx_automodapi.automodapi', 112 | 'sphinx.ext.napoleon' ] 113 | 114 | # Add any paths that contain templates here, relative to this directory. 115 | templates_path = ['_templates'] 116 | 117 | # The suffix(es) of source filenames. 118 | # You can specify multiple suffix as a list of string: 119 | # 120 | source_suffix = ['.rst', '.md'] 121 | # source_suffix = '.rst' 122 | 123 | # The master toctree document. 124 | master_doc = 'index' 125 | 126 | # General information about the project. 127 | project = mytitle 128 | copyright = u"2019, Toni Giorgino" 129 | author = u"Toni Giorgino" 130 | 131 | # The version info for the project you're documenting, acts as replacement 132 | # for |version| and |release|, also used in various other places throughout 133 | # the built documents. 134 | # 135 | # The short X.Y version. 136 | version = dtw.__version__ 137 | # The full version, including alpha/beta/rc tags. 138 | release = dtw.__version__ 139 | 140 | # The language for content autogenerated by Sphinx. Refer to documentation 141 | # for a list of supported languages. 142 | # 143 | # This is also used if you do content translation via gettext catalogs. 144 | # Usually you set "language" from the command line for these cases. 145 | language = None 146 | 147 | # List of patterns, relative to source directory, that match files and 148 | # directories to ignore when looking for source files. 149 | # This patterns also effect to html_static_path and html_extra_path 150 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 151 | 152 | # The name of the Pygments (syntax highlighting) style to use. 153 | pygments_style = 'sphinx' 154 | 155 | # If true, `todo` and `todoList` produce output, else they produce nothing. 156 | todo_include_todos = False 157 | 158 | 159 | # -- Options for HTML output ------------------------------------------- 160 | 161 | # The theme to use for HTML and HTML Help pages. See the documentation for 162 | # a list of builtin themes. 163 | # 164 | html_theme = 'bootstrap' 165 | 166 | # Theme options are theme-specific and customize the look and feel of a 167 | # theme further. For a list of options available for each theme, see the 168 | # documentation. 169 | # 170 | # html_theme_options = {} 171 | 172 | # Add any paths that contain custom static files (such as style sheets) here, 173 | # relative to this directory. They are copied after the builtin static files, 174 | # so a file named "default.css" will overwrite the builtin "default.css". 175 | html_static_path = ['_static'] 176 | 177 | 178 | # -- Options for HTMLHelp output --------------------------------------- 179 | 180 | # Output file base name for HTML help builder. 181 | htmlhelp_basename = 'dtwdoc' 182 | 183 | 184 | # -- Options for LaTeX output ------------------------------------------ 185 | 186 | latex_elements = { 187 | # The paper size ('letterpaper' or 'a4paper'). 188 | # 189 | # 'papersize': 'letterpaper', 190 | 191 | # The font size ('10pt', '11pt' or '12pt'). 192 | # 193 | # 'pointsize': '10pt', 194 | 195 | # Additional stuff for the LaTeX preamble. 196 | # 197 | # 'preamble': '', 198 | 199 | # Latex figure (float) alignment 200 | # 201 | # 'figure_align': 'htbp', 202 | } 203 | 204 | # Grouping the document tree into LaTeX files. List of tuples 205 | # (source start file, target name, title, author, documentclass 206 | # [howto, manual, or own class]). 207 | latex_documents = [ 208 | (master_doc, 'dtw.tex', 209 | mytitle, 210 | u'Toni Giorgino', 'manual'), 211 | ] 212 | 213 | 214 | # -- Options for manual page output ------------------------------------ 215 | 216 | # One entry per manual page. List of tuples 217 | # (source start file, name, description, authors, manual section). 218 | man_pages = [ 219 | (master_doc, 'dtw', 220 | mytitle, 221 | [author], 1) 222 | ] 223 | 224 | 225 | # -- Options for Texinfo output ---------------------------------------- 226 | 227 | # Grouping the document tree into Texinfo files. List of tuples 228 | # (source start file, target name, title, author, 229 | # dir menu entry, description, category) 230 | texinfo_documents = [ 231 | (master_doc, 'dtw', 232 | mytitle, 233 | author, 234 | 'dtw', 235 | 'Comprehensive implementation of Dynamic Time Warping algorithms.', 236 | 'Miscellaneous'), 237 | ] 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: ../README.rst 3 | 4 | 5 | API 6 | === 7 | 8 | .. automodapi:: dtw 9 | :no-inheritance-diagram: 10 | 11 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=dtw 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /dtw/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for the Comprehensive Dynamic Time Warp library. 4 | 5 | Please see the help for the dtw.dtw() function which is the package's 6 | main entry point. 7 | 8 | """ 9 | 10 | __author__ = """Toni Giorgino""" 11 | __email__ = 'toni.giorgino@gmail.com' 12 | __version__ = '1.5.3' 13 | 14 | # There are no comments in this package because it mirrors closely the R sources. 15 | 16 | # List of things to export on "from dtw import *" 17 | from dtw.dtw import * 18 | from dtw.stepPattern import * 19 | from dtw.countPaths import * 20 | from dtw.dtwPlot import * 21 | from dtw.mvm import * 22 | from dtw.warp import * 23 | from dtw.warpArea import * 24 | from dtw.window import * 25 | from dtw import dtw_test_data 26 | 27 | import sys 28 | 29 | # Only print in interactive mode 30 | # https://stackoverflow.com/questions/2356399/tell-if-python-is-in-interactive-mode/2356427#2356427 31 | import __main__ as main 32 | if bool(getattr(sys, 'ps1', sys.flags.interactive)): 33 | print("""Importing the dtw module. When using in academic works please cite: 34 | T. Giorgino. Computing and Visualizing Dynamic Time Warping Alignments in R: The dtw Package. 35 | J. Stat. Soft., doi:10.18637/jss.v031.i07.\n""") 36 | 37 | 38 | -------------------------------------------------------------------------------- /dtw/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Console script for dtw.""" 4 | import sys 5 | import numpy 6 | import dtw 7 | import argparse 8 | 9 | 10 | def main2(query, reference, step_pattern): 11 | """Console script for dtw.""" 12 | 13 | q = numpy.genfromtxt(query) 14 | r = numpy.genfromtxt(reference) 15 | al = dtw.dtw(q, r, step_pattern=step_pattern) 16 | 17 | wp = numpy.vstack([al.index1, al.index2]) 18 | 19 | out = "" 20 | if hasattr(al,"normalizedDistance"): 21 | out += f"Normalized distance: {al.normalizedDistance:.4g}\n\n" 22 | 23 | out += f"Distance: {al.distance:.4g}\n\n" 24 | out += f"Warping path: {wp}\n\n" 25 | 26 | return out 27 | 28 | def main(): 29 | parser = argparse.ArgumentParser(description='Command line DTW utility.', 30 | epilog="\nThe Python and R interfaces provide the full functionality, including plots.\n"+\ 31 | "See https://dynamictimewarping.github.io/\n\n") 32 | parser.add_argument("query", help="Query timeseries (tsv)") 33 | parser.add_argument("reference", help="Reference timeseries (tsv)") 34 | parser.add_argument("--step_pattern", default="symmetric2", help="Step pattern, aka recursion rule. E.g. symmetric2, asymmetric, ...") 35 | 36 | if len(sys.argv)==1: 37 | parser.print_help(sys.stderr) 38 | sys.exit(1) 39 | 40 | opts = parser.parse_args() 41 | out=main2(opts.query, opts.reference, opts.step_pattern) 42 | 43 | print(out) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | 49 | -------------------------------------------------------------------------------- /dtw/_backtrack.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | 21 | import numpy 22 | 23 | _INT_MIN = numpy.iinfo(numpy.int32).min 24 | 25 | # This is O(n). Let's not make it unreadable. 26 | def _backtrack(gcm): 27 | n = gcm.costMatrix.shape[0] 28 | m = gcm.costMatrix.shape[1] 29 | i = n - 1 30 | j = gcm.jmin 31 | 32 | # Drop null deltas 33 | dir = gcm.stepPattern.mx 34 | dir = dir[numpy.bitwise_or(dir[:, 1] != 0, 35 | dir[:, 2] != 0), :] 36 | 37 | # Split by 1st column 38 | npat = gcm.stepPattern.get_n_patterns() 39 | stepsCache = dict() 40 | for q in range(1, npat + 1): 41 | tmp = dir[dir[:, 0] == q,] 42 | stepsCache[q] = numpy.array(tmp[:, [1, 2]], 43 | dtype=int) 44 | stepsCache[q] = numpy.flip(stepsCache[q],0) 45 | 46 | # Mapping lists 47 | iis = [i] 48 | ii = [i] 49 | jjs = [j] 50 | jj = [j] 51 | ss = [] 52 | 53 | while True: 54 | if i == 0 and j == 0: break 55 | 56 | # Direction taken, 1-based 57 | s = gcm.directionMatrix[i, j] 58 | 59 | if s == _INT_MIN: break # int nan in R 60 | 61 | # undo the steps 62 | ss.insert(0, s) 63 | steps = stepsCache[s] 64 | 65 | ns = steps.shape[0] 66 | for k in range(ns): 67 | ii.insert(0, i - steps[k, 0]) 68 | jj.insert(0, j - steps[k, 1]) 69 | 70 | i -= steps[k, 0] 71 | j -= steps[k, 1] 72 | 73 | iis.insert(0, i) 74 | jjs.insert(0, j) 75 | 76 | out = {'index1': numpy.array(ii), 77 | 'index2': numpy.array(jj), 78 | 'index1s': numpy.array(iis), 79 | 'index2s': numpy.array(jjs), 80 | 'stepsTaken': numpy.array(ss)} 81 | 82 | return (out) 83 | -------------------------------------------------------------------------------- /dtw/_dtw_utils.pyx: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | # cython: language_level=3 21 | 22 | """Utility functions for DTW alignments.""" 23 | 24 | # Author: Toni Giorgino 2018 25 | # 26 | # If you use this software in academic work, please cite: 27 | # * T. Giorgino. Computing and Visualizing Dynamic Time Warping 28 | # Alignments in R: The dtw Package. Journal of Statistical 29 | # Software, v. 31, Issue 7, p. 1 - 24, aug. 2009. ISSN 30 | # 1548-7660. doi:10.18637/jss.v031.i07. http://www.jstatsoft.org/v31/i07/ 31 | 32 | 33 | import warnings 34 | 35 | import numpy as np 36 | cimport numpy as np 37 | 38 | # from cpython cimport array 39 | 40 | __all__ = ["_computeCM_wrapper"] 41 | 42 | cdef extern from "dtw_core.h": 43 | void computeCM( 44 | const int *s, 45 | const int *wm, 46 | const double *lm, 47 | const int *nstepsp, 48 | const double *dir, 49 | double *cm, # IN+OUT 50 | int *sm # OUT 51 | ) 52 | 53 | 54 | 55 | 56 | 57 | def _computeCM_wrapper(int [:,::1] wm not None, 58 | double [:,::1] lm not None, 59 | int [:] nstepsp not None, 60 | double [::1] dir not None, 61 | double [:,::1] cm not None, 62 | int [:,::1] sm = None ): 63 | 64 | # Memory ordering is transposed (fortran-like in R). 65 | st = np.array([wm.shape[1], 66 | wm.shape[0]], dtype=np.int32) 67 | cdef int [:] s = st 68 | 69 | sm = np.full_like(lm.base, -1, dtype=np.int32) 70 | 71 | computeCM(&s[0], 72 | &wm[0,0], 73 | &lm[0,0], 74 | &nstepsp[0], 75 | &dir[0], 76 | &cm[0,0], 77 | &sm[0,0]) 78 | 79 | return { 'costMatrix': cm.base, 80 | 'directionMatrix': sm.base } 81 | 82 | 83 | 84 | 85 | def _test_computeCM(TS=5): 86 | 87 | DTYPE = np.int32 88 | 89 | twm = np.ones((TS, TS), dtype=DTYPE) 90 | 91 | tlm = np.zeros( (TS,TS), dtype=np.double) 92 | for i in range(TS): 93 | for j in range(TS): 94 | tlm[i,j]=(i+1)*(j+1) 95 | 96 | tnstepsp = np.array([6], dtype=DTYPE) 97 | 98 | tdir = np.array( (1, 1, 2, 2, 3, 3, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,-1, 1,-1, 1,-1, 1), 99 | dtype=np.double) 100 | 101 | tcm = np.full_like(tlm, np.nan, dtype=np.double) 102 | tcm[0,0] = tlm[0,0] 103 | 104 | out = _computeCM_wrapper(twm, 105 | tlm, 106 | tnstepsp, 107 | tdir, 108 | tcm) 109 | return out 110 | 111 | 112 | -------------------------------------------------------------------------------- /dtw/_globalCostMatrix.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from dtw.window import noWindow 3 | from dtw._dtw_utils import _computeCM_wrapper 4 | 5 | 6 | def _globalCostMatrix(lm, 7 | step_pattern, 8 | window_function, 9 | seed, 10 | win_args): 11 | 12 | ITYPE = numpy.int32 13 | n, m = lm.shape 14 | 15 | if window_function == noWindow: # for performance 16 | wm = numpy.full_like(lm, 1, dtype=ITYPE) 17 | else: 18 | ix, jx = numpy.indices((n,m)) 19 | wm = window_function(ix, jx, 20 | query_size=n, 21 | reference_size=m, 22 | **win_args) 23 | wm = wm.astype(ITYPE) # Convert False/True to 0/1 24 | 25 | nsteps = numpy.array([step_pattern.get_n_rows()], dtype=ITYPE) 26 | 27 | dir = numpy.array(step_pattern._get_p(), dtype=numpy.double) 28 | 29 | if seed is not None: 30 | cm = seed 31 | else: 32 | cm = numpy.full_like(lm, numpy.nan, dtype=numpy.double) 33 | cm[0, 0] = lm[0, 0] 34 | 35 | # All input arguments 36 | out = _computeCM_wrapper(wm, 37 | lm, 38 | nsteps, 39 | dir, 40 | cm) 41 | 42 | out['stepPattern'] = step_pattern 43 | return out 44 | 45 | 46 | def _test_computeCM2(TS=5): 47 | import numpy as np 48 | ITYPE = np.int32 49 | 50 | twm = np.ones((TS, TS), dtype=ITYPE) 51 | 52 | tlm = np.zeros((TS, TS), dtype=np.double) 53 | for i in range(TS): 54 | for j in range(TS): 55 | tlm[i, j] = (i + 1) * (j + 1) 56 | 57 | tnstepsp = np.array([6], dtype=ITYPE) 58 | 59 | tdir = np.array((1, 1, 2, 2, 3, 3, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, -1, 1, -1, 1, -1, 1), 60 | dtype=np.double) 61 | 62 | tcm = np.full_like(tlm, np.nan, dtype=np.double) 63 | tcm[0, 0] = tlm[0, 0] 64 | 65 | out = _computeCM_wrapper(twm, 66 | tlm, 67 | tnstepsp, 68 | tdir, 69 | tcm) 70 | return out 71 | -------------------------------------------------------------------------------- /dtw/countPaths.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """Count the number of warping paths consistent with the constraints.""" 21 | 22 | import numpy 23 | 24 | 25 | def countPaths(d, debug=False): 26 | # IMPORT_RDOCSTRING countPaths 27 | """Count the number of warping paths consistent with the constraints. 28 | 29 | Count how many possible warping paths exist in the alignment problem 30 | passed as an argument. The object passed as an argument is used to look 31 | up the problem parameters such as the used step pattern, windowing, open 32 | ends, and so on. The actual alignment is ignored. 33 | 34 | **Details** 35 | 36 | Note that the number of paths grows exponentially with problems size. 37 | The result may be approximate when windowing functions are used. 38 | 39 | If ``debug=True``, a matrix used for the computation is returned instead 40 | of the final result. 41 | 42 | Parameters 43 | ---------- 44 | d : 45 | an object of class `dtw` 46 | debug : 47 | return an intermediate result 48 | 49 | Returns 50 | ------- 51 | 52 | The number of paths. 53 | 54 | Examples 55 | -------- 56 | 57 | >>> from dtw import * 58 | >>> ds = dtw( numpy.arange(3,10), numpy.arange(1,9), 59 | ... keep_internals=True, step_pattern=asymmetric); 60 | >>> countPaths(ds) 61 | 126 62 | 63 | """ 64 | # ENDIMPORT 65 | N = d.N 66 | M = d.M 67 | m = numpy.full((N, M), numpy.nan) 68 | 69 | if d.openBegin: 70 | m[0, :] = 1.0 71 | else: 72 | m[0, 0] = 1.0 73 | 74 | dir = d.stepPattern 75 | npats = dir.get_n_patterns() 76 | # nsteps = dir.get_n_rows() 77 | deltas = dir._mkDirDeltas() 78 | 79 | wf = d.windowFunction 80 | 81 | for ii in range(N): 82 | for jj in range(M): 83 | if numpy.isfinite(m[ii, jj]): 84 | continue 85 | 86 | if not wf(ii, jj, 87 | query_size=N, 88 | reference_size=M, 89 | **d.windowArgs): 90 | m[ii, jj] = 0 91 | continue 92 | 93 | np = 0 94 | for k in range(npats): 95 | ni = ii - deltas[k, 0] 96 | nj = jj - deltas[k, 1] 97 | 98 | if ni >= 0 and nj >= 0: 99 | np += m[ni, nj] 100 | 101 | m[ii, jj] = np 102 | 103 | if debug: 104 | return m 105 | 106 | if d.openEnd: 107 | r = numpy.sum(m[-1,]) 108 | else: 109 | r = m[-1, -1] 110 | return int(r) 111 | -------------------------------------------------------------------------------- /dtw/data/README.txt: -------------------------------------------------------------------------------- 1 | ANSI/AAMI EC13 Test Waveforms 2 | ============================= 3 | 4 | 5 | Data made available under the Open Data Commons Attribution License v1.0 6 | by [PysioNet](https://www.physionet.org/content/aami-ec13/1.0.0/). 7 | 8 | 9 | 10 | Citation 11 | -------- 12 | 13 | > Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark 14 | RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank, 15 | PhysioToolkit, and PhysioNet: Components of a New Research Resource 16 | for Complex Physiologic Signals 17 | (2003). Circulation. 101(23):e215-e220. 18 | 19 | -------------------------------------------------------------------------------- /dtw/dtw.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | 21 | # Author: Toni Giorgino 2018 22 | # 23 | # If you use this software in academic work, please cite: 24 | # * T. Giorgino. Computing and Visualizing Dynamic Time Warping 25 | # Alignments in R: The dtw Package. Journal of Statistical 26 | # Software, v. 31, Issue 7, p. 1 - 24, aug. 2009. ISSN 27 | # 1548-7660. doi:10.18637/jss.v031.i07. http://www.jstatsoft.org/v31/i07/ 28 | 29 | """Main dtw module""" 30 | 31 | import numpy 32 | import sys 33 | 34 | from dtw.stepPattern import * 35 | from dtw._backtrack import _backtrack 36 | from dtw._globalCostMatrix import _globalCostMatrix 37 | from dtw.window import * 38 | from dtw.dtwPlot import * 39 | 40 | import scipy.spatial.distance 41 | 42 | 43 | # -------------------- 44 | 45 | class DTW: 46 | """The results of an alignment operation. 47 | 48 | Objects of class DTW contain alignments computed by the [dtw()] 49 | function. 50 | 51 | **Attributes:** 52 | 53 | - ``distance`` the minimum global distance computed, *not* normalized. 54 | - ``normalizedDistance`` distance computed, *normalized* for path 55 | length, if normalization is known for chosen step pattern. 56 | - ``N,M`` query and reference length 57 | - ``call`` the function call that created the object 58 | - ``index1`` matched elements: indices in ``x`` 59 | - ``index2`` corresponding mapped indices in ``y`` 60 | - ``stepPattern`` the ``stepPattern`` object used for the computation 61 | - ``jmin`` last element of reference matched, if ``open_end=True`` 62 | - ``directionMatrix`` if ``keep_internals=True``, the directions of 63 | steps that would be taken at each alignment pair (integers indexing 64 | production rules in the chosen step pattern) 65 | - ``stepsTaken`` the list of steps taken from the beginning to the end 66 | of the alignment (integers indexing chosen step pattern) 67 | - ``index1s, index2s`` same as ``index1/2``, excluding intermediate 68 | steps for multi-step patterns like [asymmetricP05()] 69 | - ``costMatrix`` if ``keep_internals=True``, the cumulative cost matrix 70 | - ``query, reference`` if ``keep_internals=True`` and passed as the 71 | ``x`` and ``y`` arguments, the query and reference timeseries. 72 | 73 | """ 74 | 75 | def __init__(self, obj): 76 | self.__dict__.update(obj) # Convert dict to object 77 | 78 | def __repr__(self): 79 | s = "DTW alignment object of size (query x reference): {:d} x {:d}".format(self.N, self.M) 80 | return (s) 81 | 82 | def plot(self, type="alignment", **kwargs): 83 | # IMPORT_RDOCSTRING plot.dtw 84 | """Plotting of dynamic time warp results 85 | 86 | Methods for plotting dynamic time warp alignment objects returned by 87 | [dtw()]. 88 | 89 | **Details** 90 | 91 | ``dtwPlot`` displays alignment contained in ``dtw`` objects. 92 | 93 | Various plotting styles are available, passing strings to the ``type`` 94 | argument (may be abbreviated): 95 | 96 | - ``alignment`` plots the warping curve in ``d``; 97 | - ``twoway`` plots a point-by-point comparison, with matching lines; 98 | see [dtwPlotTwoWay()]; 99 | - ``threeway`` vis-a-vis inspection of the timeseries and their warping 100 | curve; see [dtwPlotThreeWay()]; 101 | - ``density`` displays the cumulative cost landscape with the warping 102 | path overimposed; see [dtwPlotDensity()] 103 | 104 | Additional parameters are passed to the plotting functions: use with 105 | care. 106 | 107 | Parameters 108 | ---------- 109 | x,d : 110 | `dtw` object, usually result of call to [dtw()] 111 | xlab : 112 | label for the query axis 113 | ylab : 114 | label for the reference axis 115 | type : 116 | general style for the plot, see below 117 | plot_type : 118 | type of line to be drawn, used as the `type` argument in the underlying `plot` call 119 | ... : 120 | additional arguments, passed to plotting functions 121 | 122 | """ 123 | # ENDIMPORT 124 | return dtwPlot(self, type, **kwargs) 125 | 126 | 127 | # -------------------- 128 | 129 | 130 | def dtw(x, y=None, 131 | dist_method="euclidean", 132 | step_pattern="symmetric2", 133 | window_type=None, 134 | window_args={}, 135 | keep_internals=False, 136 | distance_only=False, 137 | open_end=False, 138 | open_begin=False): 139 | """Compute Dynamic Time Warp and find optimal alignment between two time 140 | series. 141 | 142 | **Details** 143 | 144 | The function performs Dynamic Time Warp (DTW) and computes the optimal 145 | alignment between two time series ``x`` and ``y``, given as numeric 146 | vectors. The “optimal” alignment minimizes the sum of distances between 147 | aligned elements. Lengths of ``x`` and ``y`` may differ. 148 | 149 | The local distance between elements of ``x`` (query) and ``y`` 150 | (reference) can be computed in one of the following ways: 151 | 152 | 1. if ``dist_method`` is a string, ``x`` and ``y`` are passed to the 153 | `scipy.spatial.distance.cdist` function with the method given; 154 | 2. multivariate time series and arbitrary distance metrics can be 155 | handled by supplying a local-distance matrix. Element ``[i,j]`` of 156 | the local-distance matrix is understood as the distance between 157 | element ``x[i]`` and ``y[j]``. The distance matrix has therefore 158 | ``n=length(x)`` rows and ``m=length(y)`` columns (see note below). 159 | 160 | Several common variants of the DTW recursion are supported via the 161 | ``step_pattern`` argument, which defaults to ``symmetric2``. Step 162 | patterns are commonly used to *locally* constrain the slope of the 163 | alignment function. See [stepPattern()] for details. 164 | 165 | Windowing enforces a *global* constraint on the envelope of the warping 166 | path. It is selected by passing a string or function to the 167 | ``window_type`` argument. Commonly used windows are (abbreviations 168 | allowed): 169 | 170 | - ``"none"`` No windowing (default) 171 | - ``"sakoechiba"`` A band around main diagonal 172 | - ``"slantedband"`` A band around slanted diagonal 173 | - ``"itakura"`` So-called Itakura parallelogram 174 | 175 | ``window_type`` can also be an user-defined windowing function. See 176 | [dtwWindowingFunctions()] for all available windowing functions, details 177 | on user-defined windowing, and a discussion of the (mis)naming of the 178 | “Itakura” parallelogram as a global constraint. Some windowing functions 179 | may require parameters, such as the ``window_size`` argument. 180 | 181 | Open-ended alignment, i_e. semi-unconstrained alignment, can be selected 182 | via the ``open_end`` switch. Open-end DTW computes the alignment which 183 | best matches all of the query with a *leading part* of the reference. 184 | This is proposed e_g. by Mori (2006), Sakoe (1979) and others. 185 | Similarly, open-begin is enabled via ``open_begin``; it makes sense when 186 | ``open_end`` is also enabled (subsequence finding). Subsequence 187 | alignments are similar e_g. to UE2-1 algorithm by Rabiner (1978) and 188 | others. Please find a review in Tormene et al. (2009). 189 | 190 | If the warping function is not required, computation can be sped up 191 | enabling the ``distance_only=True`` switch, which skips the backtracking 192 | step. The output object will then lack the ``index{1,2,1s,2s}`` and 193 | ``stepsTaken`` fields. 194 | 195 | 196 | Parameters 197 | ---------- 198 | x : 199 | query vector *or* local cost matrix 200 | y : 201 | reference vector, unused if `x` given as cost matrix 202 | dist_method : 203 | pointwise (local) distance function to use. 204 | step_pattern : 205 | a stepPattern object describing the local warping steps 206 | allowed with their cost (see [stepPattern()]) 207 | window_type : 208 | windowing function. Character: "none", "itakura", 209 | "sakoechiba", "slantedband", or a function (see details). 210 | open_begin,open_end : 211 | perform open-ended alignments 212 | keep_internals : 213 | preserve the cumulative cost matrix, inputs, and other 214 | internal structures 215 | distance_only : 216 | only compute distance (no backtrack, faster) 217 | window_args : 218 | additional arguments, passed to the windowing function 219 | 220 | Returns 221 | ------- 222 | 223 | An object of class ``DTW``. See docs for the corresponding properties. 224 | 225 | 226 | Notes 227 | ----- 228 | 229 | Cost matrices (both input and output) have query elements arranged 230 | row-wise (first index), and reference elements column-wise (second 231 | index). They print according to the usual convention, with indexes 232 | increasing down- and rightwards. Many DTW papers and tutorials show 233 | matrices according to plot-like conventions, i_e. reference index 234 | growing upwards. This may be confusing. 235 | 236 | A fast compiled version of the function is normally used. Should it be 237 | unavailable, the interpreted equivalent will be used as a fall-back with 238 | a warning. 239 | 240 | References 241 | ---------- 242 | 243 | 1. Toni Giorgino. *Computing and Visualizing Dynamic Time Warping 244 | Alignments in R: The dtw Package.* Journal of Statistical Software, 245 | 31(7), 1-24. http://www.jstatsoft.org/v31/i07/ 246 | 2. Tormene, P.; Giorgino, T.; Quaglini, S. & Stefanelli, M. *Matching 247 | incomplete time series with dynamic time warping: an algorithm and an 248 | application to post-stroke rehabilitation.* Artif Intell Med, 2009, 249 | 45, 11-34. http://dx.doi.org/10.1016/j.artmed.2008.11.007 250 | 3. Sakoe, H.; Chiba, S., *Dynamic programming algorithm optimization for 251 | spoken word recognition,* Acoustics, Speech, and Signal Processing, 252 | IEEE Transactions on , vol.26, no.1, pp. 43-49, Feb 1978. 253 | http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=1163055 254 | 4. Mori, A.; Uchida, S.; Kurazume, R.; Taniguchi, R.; Hasegawa, T. & 255 | Sakoe, H. *Early Recognition and Prediction of Gestures* Proc. 18th 256 | International Conference on Pattern Recognition ICPR 2006, 2006, 3, 257 | 560-563 258 | 5. Sakoe, H. *Two-level DP-matching–A dynamic programming-based pattern 259 | matching algorithm for connected word recognition* Acoustics, Speech, 260 | and Signal Processing, IEEE Transactions on, 1979, 27, 588-595 261 | 6. Rabiner L, Rosenberg A, Levinson S (1978). *Considerations in dynamic 262 | time warping algorithms for discrete word recognition.* IEEE Trans. 263 | Acoust., Speech, Signal Process., 26(6), 575-582. ISSN 0096-3518. 264 | 7. Muller M. *Dynamic Time Warping* in *Information Retrieval for Music 265 | and Motion*. Springer Berlin Heidelberg; 2007. p. 69-84. 266 | http://link.springer.com/chapter/10.1007/978-3-540-74048-3_4 267 | 268 | Examples 269 | -------- 270 | 271 | 272 | 273 | >>> import numpy as np 274 | >>> from dtw import * 275 | 276 | A noisy sine wave as query 277 | 278 | >>> idx = np.linspace(0,6.28,num=100) 279 | >>> query = np.sin(idx) + np.random.uniform(size=100)/10.0 280 | 281 | A cosine is for reference; sin and cos are offset by 25 samples 282 | 283 | >>> reference = np.cos(idx) 284 | 285 | Find the best match 286 | 287 | >>> alignment = dtw(query,reference) 288 | 289 | Display the mapping, AKA warping function - may be multiple-valued 290 | Equivalent to: plot(alignment,type="alignment") 291 | 292 | >>> import matplotlib.pyplot as plt; 293 | ... plt.plot(alignment.index1, alignment.index2) # doctest: +SKIP 294 | 295 | 296 | Partial alignments are allowed. 297 | 298 | >>> alignmentOBE = dtw(query[44:88], reference, 299 | ... keep_internals=True, 300 | ... step_pattern=asymmetric, 301 | ... open_end=True,open_begin=True) 302 | 303 | >>> alignmentOBE.plot(type="twoway",offset=1) # doctest: +SKIP 304 | 305 | 306 | Subsetting allows warping and unwarping of 307 | timeseries according to the warping curve. 308 | See first example below. 309 | 310 | Most useful: plot the warped query along with reference 311 | 312 | >>> plt.plot(reference); 313 | ... plt.plot(alignment.index2,query[alignment.index1]) # doctest: +SKIP 314 | 315 | Plot the (unwarped) query and the inverse-warped reference 316 | 317 | >>> plt.plot(query) # doctest: +SKIP 318 | ... plt.plot(alignment.index1,reference[alignment.index2]) 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | A hand-checkable example 328 | 329 | >>> ldist = np.ones((6,6)) # Matrix of ones 330 | >>> ldist[1,:] = 0; ldist[:,4] = 0; # Mark a clear path of zeroes 331 | >>> ldist[1,4] = .01; # Forcely cut the corner 332 | 333 | >>> ds = dtw(ldist); # DTW with user-supplied local 334 | 335 | >>> da = dtw(ldist,step_pattern=asymmetric) # Also compute the asymmetric 336 | 337 | Symmetric: alignment follows the low-distance marked path 338 | 339 | >>> plt.plot(ds.index1,ds.index2) # doctest: +SKIP 340 | 341 | Asymmetric: visiting 1 is required twice 342 | 343 | >>> plt.plot(da.index1,da.index2,'ro') # doctest: +SKIP 344 | 345 | >>> float(ds.distance) 346 | 2.0 347 | >>> float(da.distance) 348 | 2.0 349 | 350 | """ 351 | 352 | 353 | if y is None: 354 | x = numpy.array(x) 355 | if len(x.shape) != 2: 356 | _error("A 2D local distance matrix was expected") 357 | lm = numpy.array(x) 358 | else: 359 | x2, y2 = numpy.atleast_2d(x), numpy.atleast_2d(y) 360 | if x2.shape[0] == 1: 361 | x2 = x2.T 362 | if y2.shape[0] == 1: 363 | y2 = y2.T 364 | lm = scipy.spatial.distance.cdist(x2, y2, metric=dist_method) 365 | 366 | wfun = _canonicalizeWindowFunction(window_type) 367 | 368 | step_pattern = _canonicalizeStepPattern(step_pattern) 369 | norm = step_pattern.hint 370 | 371 | n, m = lm.shape 372 | 373 | if open_begin: 374 | if norm != "N": 375 | _error( 376 | "Open-begin requires step patterns with 'N' normalization (e.g. asymmetric, or R-J types (c)). See Tormene et al.") 377 | lm = numpy.vstack([numpy.zeros((1, lm.shape[1])), lm]) # prepend null row 378 | np = n + 1 379 | precm = numpy.full_like(lm, numpy.nan, dtype=numpy.double) 380 | precm[0, :] = 0 381 | else: 382 | precm = None 383 | np = n 384 | 385 | gcm = _globalCostMatrix(lm, 386 | step_pattern=step_pattern, 387 | window_function=wfun, 388 | seed=precm, 389 | win_args=window_args) 390 | gcm = DTW(gcm) # turn into an object, use dot to access properties 391 | 392 | gcm.N = n 393 | gcm.M = m 394 | 395 | gcm.openEnd = open_end 396 | gcm.openBegin = open_begin 397 | gcm.windowFunction = wfun 398 | gcm.windowArgs = window_args # py 399 | 400 | # misnamed 401 | lastcol = gcm.costMatrix[-1,] 402 | 403 | if norm == "NA": 404 | pass 405 | elif norm == "N+M": 406 | lastcol = lastcol / (n + numpy.arange(m) + 1) 407 | elif norm == "N": 408 | lastcol = lastcol / n 409 | elif norm == "M": 410 | lastcol = lastcol / (1 + numpy.arange(m)) 411 | 412 | gcm.jmin = m - 1 413 | 414 | if open_end: 415 | if norm == "NA": 416 | _error("Open-end alignments require normalizable step patterns") 417 | gcm.jmin = numpy.nanargmin(lastcol) 418 | 419 | gcm.distance = gcm.costMatrix[-1, gcm.jmin] 420 | 421 | if numpy.isnan(gcm.distance): 422 | _error("No warping path found compatible with the local constraints") 423 | 424 | if step_pattern.hint != "NA": 425 | gcm.normalizedDistance = lastcol[gcm.jmin] 426 | else: 427 | gcm.normalizedDistance = numpy.nan 428 | 429 | if not distance_only: 430 | mapping = _backtrack(gcm) 431 | gcm.__dict__.update(mapping) 432 | 433 | if open_begin: 434 | gcm.index1 = gcm.index1[1:] - 1 435 | gcm.index1s = gcm.index1s[1:] - 1 436 | gcm.index2 = gcm.index2[1:] 437 | gcm.index2s = gcm.index2s[1:] 438 | lm = lm[1:, :] 439 | gcm.costMatrix = gcm.costMatrix[1:, :] 440 | gcm.directionMatrix = gcm.directionMatrix[1:, :] 441 | 442 | if not keep_internals: 443 | del gcm.costMatrix 444 | del gcm.directionMatrix 445 | else: 446 | gcm.localCostMatrix = lm 447 | if y is not None: 448 | gcm.query = x 449 | gcm.reference = y 450 | 451 | return gcm 452 | 453 | 454 | # Return a callable object representing the window 455 | def _canonicalizeWindowFunction(window_type): 456 | if callable(window_type): 457 | return window_type 458 | 459 | if window_type is None: 460 | return noWindow 461 | 462 | return { 463 | "none": noWindow, 464 | "sakoechiba": sakoeChibaWindow, 465 | "itakura": itakuraWindow, 466 | "slantedband": slantedBandWindow 467 | }.get(window_type, lambda: _error("Window function undefined")) 468 | 469 | 470 | def _canonicalizeStepPattern(s): 471 | """Return object by string""" 472 | if hasattr(s,"mx"): 473 | return s 474 | else: 475 | return getattr(sys.modules["dtw.stepPattern"], s) 476 | 477 | 478 | # Kludge because lambda: raise doesn't work 479 | def _error(s): 480 | raise ValueError(s) 481 | -------------------------------------------------------------------------------- /dtw/dtwPlot.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """DTW plotting functions""" 21 | 22 | import numpy 23 | 24 | def dtwPlot(x, type="alignment", **kwargs): 25 | # IMPORT_RDOCSTRING plot.dtw 26 | """Plotting of dynamic time warp results 27 | 28 | Methods for plotting dynamic time warp alignment objects returned by 29 | [dtw()]. 30 | 31 | **Details** 32 | 33 | ``dtwPlot`` displays alignment contained in ``dtw`` objects. 34 | 35 | Various plotting styles are available, passing strings to the ``type`` 36 | argument (may be abbreviated): 37 | 38 | - ``alignment`` plots the warping curve in ``d``; 39 | - ``twoway`` plots a point-by-point comparison, with matching lines; 40 | see [dtwPlotTwoWay()]; 41 | - ``threeway`` vis-a-vis inspection of the timeseries and their warping 42 | curve; see [dtwPlotThreeWay()]; 43 | - ``density`` displays the cumulative cost landscape with the warping 44 | path overimposed; see [dtwPlotDensity()] 45 | 46 | Additional parameters are passed to the plotting functions: use with 47 | care. 48 | 49 | Parameters 50 | ---------- 51 | x,d : 52 | `dtw` object, usually result of call to [dtw()] 53 | xlab : 54 | label for the query axis 55 | ylab : 56 | label for the reference axis 57 | type : 58 | general style for the plot, see below 59 | plot_type : 60 | type of line to be drawn, used as the `type` argument in the underlying `plot` call 61 | ... : 62 | additional arguments, passed to plotting functions 63 | 64 | """ 65 | # ENDIMPORT 66 | 67 | if type == "alignment": 68 | return dtwPlotAlignment(x, **kwargs) 69 | elif type == "twoway": 70 | return dtwPlotTwoWay(x, **kwargs) 71 | elif type == "threeway": 72 | return dtwPlotThreeWay(x, **kwargs) 73 | elif type == "density": 74 | return dtwPlotDensity(x, **kwargs) 75 | else: 76 | raise ValueError("Unknown plot type: " + type) 77 | 78 | 79 | def dtwPlotAlignment(d, xlab="Query index", ylab="Reference index", **kwargs): 80 | import matplotlib.pyplot as plt 81 | fig, ax = plt.subplots(figsize=(6, 6)) 82 | 83 | ax.plot(d.index1, d.index2, **kwargs) 84 | ax.set_xlabel(xlab) 85 | ax.set_ylabel(ylab) 86 | 87 | return ax 88 | 89 | 90 | def dtwPlotTwoWay(d, xts=None, yts=None, 91 | offset=0, 92 | ts_type="l", 93 | match_indices=None, 94 | match_col="gray", 95 | xlab="Index", 96 | ylab="Query value", 97 | **kwargs): 98 | # IMPORT_RDOCSTRING dtwPlotTwoWay 99 | """Plotting of dynamic time warp results: pointwise comparison 100 | 101 | Display the query and reference time series and their alignment, 102 | arranged for visual inspection. 103 | 104 | **Details** 105 | 106 | The two vectors are displayed via the [matplot()] functions; their 107 | appearance can be customized via the ``type`` and ``pch`` arguments 108 | (constants or vectors of two elements). If ``offset`` is set, the 109 | reference is shifted vertically by the given amount; this will be 110 | reflected by the *right-hand* axis. 111 | 112 | Argument ``match_indices`` is used to draw a visual guide to matches; if 113 | a vector is given, guides are drawn for the corresponding indices in the 114 | warping curve (match lines). If integer, it is used as the number of 115 | guides to be plotted. The corresponding style is customized via the 116 | ``match_col`` and ``match_lty`` arguments. 117 | 118 | If ``xts`` and ``yts`` are not supplied, they will be recovered from 119 | ``d``, as long as it was created with the two-argument call of [dtw()] 120 | with ``keep_internals=True``. Only single-variate time series can be 121 | plotted this way. 122 | 123 | Parameters 124 | ---------- 125 | d : 126 | an alignment result, object of class `dtw` 127 | xts : 128 | query vector 129 | yts : 130 | reference vector 131 | xlab,ylab : 132 | axis labels 133 | offset : 134 | displacement between the timeseries, summed to reference 135 | match_col,match_lty : 136 | color and line type of the match guide lines 137 | match_indices : 138 | indices for which to draw a visual guide 139 | ts_type,pch : 140 | graphical parameters for timeseries plotting, passed to `matplot` 141 | ... : 142 | additional arguments, passed to `matplot` 143 | 144 | Notes 145 | ----- 146 | 147 | When ``offset`` is set values on the left axis only apply to the query. 148 | 149 | """ 150 | # ENDIMPORT 151 | 152 | import matplotlib.pyplot as plt 153 | from matplotlib import collections as mc 154 | 155 | if xts is None or yts is None: 156 | try: 157 | xts = d.query 158 | yts = d.reference 159 | except: 160 | raise ValueError("Original timeseries are required") 161 | 162 | # ytso = yts + offset 163 | offset = -offset 164 | 165 | xtimes = numpy.arange(len(xts)) 166 | ytimes = numpy.arange(len(yts)) 167 | 168 | fig, ax = plt.subplots() 169 | 170 | ax.set_xlabel(xlab) 171 | ax.set_ylabel(ylab) 172 | 173 | ax.plot(xtimes, numpy.array(xts), color='k', **kwargs) 174 | ax.plot(ytimes, numpy.array(yts) - offset, **kwargs) # Plot with offset applied 175 | 176 | if offset != 0: 177 | # Create an offset axis 178 | ax2 = ax.twinx() 179 | ax2.tick_params('y', colors='b') 180 | ql, qh = ax.get_ylim() 181 | ax2.set_ylim(ql + offset, qh + offset) 182 | 183 | # https://stackoverflow.com/questions/21352580/matplotlib-plotting-numerous-disconnected-line-segments-with-different-colors 184 | if match_indices is None: 185 | idx = numpy.linspace(0, len(d.index1) - 1) 186 | elif not hasattr(match_indices, "__len__"): 187 | idx = numpy.linspace(0, len(d.index1) - 1, num=match_indices) 188 | else: 189 | idx = match_indices 190 | idx = numpy.array(idx).astype(int) 191 | 192 | col = [] 193 | for i in idx: 194 | col.append([(d.index1[i], xts[d.index1[i]]), 195 | (d.index2[i], -offset + yts[d.index2[i]])]) 196 | 197 | lc = mc.LineCollection(col, linewidths=1, linestyles=":", colors=match_col) 198 | ax.add_collection(lc) 199 | 200 | return ax 201 | 202 | 203 | def dtwPlotThreeWay(d, xts=None, yts=None, 204 | match_indices=None, 205 | match_col="gray", 206 | xlab="Query index", 207 | ylab="Reference index", **kwargs): 208 | # IMPORT_RDOCSTRING dtwPlotThreeWay 209 | """Plotting of dynamic time warp results: annotated warping function 210 | 211 | Display the query and reference time series and their warping curve, 212 | arranged for visual inspection. 213 | 214 | **Details** 215 | 216 | The query time series is plotted in the bottom panel, with indices 217 | growing rightwards and values upwards. Reference is in the left panel, 218 | indices growing upwards and values leftwards. The warping curve panel 219 | matches indices, and therefore element (1,1) will be at the lower left, 220 | (N,M) at the upper right. 221 | 222 | Argument ``match_indices`` is used to draw a visual guide to matches; if 223 | a vector is given, guides are drawn for the corresponding indices in the 224 | warping curve (match lines). If integer, it is used as the number of 225 | guides to be plotted. The corresponding style is customized via the 226 | ``match_col`` and ``match_lty`` arguments. 227 | 228 | If ``xts`` and ``yts`` are not supplied, they will be recovered from 229 | ``d``, as long as it was created with the two-argument call of [dtw()] 230 | with ``keep_internals=True``. Only single-variate time series can be 231 | plotted. 232 | 233 | Parameters 234 | ---------- 235 | d : 236 | an alignment result, object of class `dtw` 237 | xts : 238 | query vector 239 | yts : 240 | reference vector 241 | xlab : 242 | label for the query axis 243 | ylab : 244 | label for the reference axis 245 | main : 246 | main title 247 | type_align : 248 | line style for warping curve plot 249 | type_ts : 250 | line style for timeseries plot 251 | match_indices : 252 | indices for which to draw a visual guide 253 | margin : 254 | outer figure margin 255 | inner_margin : 256 | inner figure margin 257 | title_margin : 258 | space on the top of figure 259 | ... : 260 | additional arguments, used for the warping curve 261 | 262 | """ 263 | # ENDIMPORT 264 | import matplotlib.pyplot as plt 265 | import matplotlib.gridspec as gridspec 266 | from matplotlib import collections as mc 267 | 268 | if xts is None or yts is None: 269 | try: 270 | xts = d.query 271 | yts = d.reference 272 | except: 273 | raise ValueError("Original timeseries are required") 274 | 275 | nn = len(xts) 276 | mm = len(yts) 277 | nn1 = numpy.arange(nn) 278 | mm1 = numpy.arange(mm) 279 | 280 | fig = plt.figure() 281 | gs = gridspec.GridSpec(2, 2, 282 | width_ratios=[1, 3], 283 | height_ratios=[3, 1]) 284 | axr = plt.subplot(gs[0]) 285 | ax = plt.subplot(gs[1]) 286 | axq = plt.subplot(gs[3]) 287 | 288 | axq.plot(nn1, xts) # query, horizontal, bottom 289 | axq.set_xlabel(xlab) 290 | 291 | axr.plot(yts, mm1) # ref, vertical 292 | axr.invert_xaxis() 293 | axr.set_ylabel(ylab) 294 | 295 | ax.plot(d.index1, d.index2) 296 | 297 | if match_indices is None: 298 | idx = [] 299 | elif not hasattr(match_indices, "__len__"): 300 | idx = numpy.linspace(0, len(d.index1) - 1, num=match_indices) 301 | else: 302 | idx = match_indices 303 | idx = numpy.array(idx).astype(int) 304 | 305 | col = [] 306 | for i in idx: 307 | col.append([(d.index1[i], 0), 308 | (d.index1[i], d.index2[i])]) 309 | col.append([(0, d.index2[i]), 310 | (d.index1[i], d.index2[i])]) 311 | 312 | lc = mc.LineCollection(col, linewidths=1, linestyles=":", colors=match_col) 313 | ax.add_collection(lc) 314 | 315 | return ax 316 | 317 | 318 | def dtwPlotDensity(d, normalize=False, 319 | xlab="Query index", 320 | ylab="Reference index", **kwargs): 321 | # IMPORT_RDOCSTRING dtwPlotDensity 322 | """Display the cumulative cost density with the warping path overimposed 323 | 324 | The plot is based on the cumulative cost matrix. It displays the optimal 325 | alignment as a “ridge” in the global cost landscape. 326 | 327 | **Details** 328 | 329 | The alignment must have been constructed with the 330 | ``keep_internals=True`` parameter set. 331 | 332 | If ``normalize`` is ``True``, the *average* cost per step is plotted 333 | instead of the cumulative one. Step averaging depends on the 334 | [stepPattern()] used. 335 | 336 | Parameters 337 | ---------- 338 | d : 339 | an alignment result, object of class `dtw` 340 | normalize : 341 | show per-step average cost instead of cumulative cost 342 | xlab : 343 | label for the query axis 344 | ylab : 345 | label for the reference axis 346 | ... : 347 | additional parameters forwarded to plotting functions 348 | 349 | Examples 350 | -------- 351 | >>> from dtw import * 352 | 353 | A study of the "Itakura" parallelogram 354 | 355 | A widely held misconception is that the "Itakura parallelogram" (as 356 | described in the original article) is a global constraint. Instead, 357 | it arises from local slope restrictions. Anyway, an "itakuraWindow", 358 | is provided in this package. A comparison between the two follows. 359 | 360 | The local constraint: three sides of the parallelogram are seen 361 | 362 | >>> (query, reference) = dtw_test_data.sin_cos() 363 | >>> ita = dtw(query, reference, keep_internals=True, step_pattern=typeIIIc) 364 | 365 | >>> dtwPlotDensity(ita) # doctest: +SKIP 366 | 367 | Symmetric step with global parallelogram-shaped constraint. Note how 368 | long (>2 steps) horizontal stretches are allowed within the window. 369 | 370 | >>> ita = dtw(query, reference, keep_internals=True, window_type=itakuraWindow) 371 | 372 | >>> dtwPlotDensity(ita) # doctest: +SKIP 373 | 374 | """ 375 | # ENDIMPORT 376 | import matplotlib.pyplot as plt 377 | 378 | try: 379 | cm = d.costMatrix 380 | except: 381 | raise ValueError("dtwPlotDensity requires dtw internals (set keep.internals=True on dtw() call)") 382 | 383 | if normalize: 384 | norm = d.stepPattern.hint 385 | row, col = numpy.indices(cm.shape) 386 | if norm == "NA": 387 | raise ValueError("Step pattern has no normalization") 388 | elif norm == "N": 389 | cm = cm / (row + 1) 390 | elif norm == "N+M": 391 | cm = cm / (row + col + 2) 392 | elif norm == "M": 393 | cm = cm / (col + 1) 394 | 395 | fig, ax = plt.subplots(figsize=(6, 6)) 396 | 397 | ax.imshow(cm.T, origin="lower", cmap=plt.get_cmap("terrain")) 398 | co = ax.contour(cm.T, colors="black", linewidths = 1) 399 | ax.clabel(co) 400 | 401 | ax.plot(d.index1, d.index2, color="blue", linewidth=2) 402 | 403 | ax.set_xlabel(xlab) 404 | ax.set_ylabel(ylab) 405 | 406 | return ax 407 | -------------------------------------------------------------------------------- /dtw/dtw_core.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2006-2022 of Toni Giorgino 3 | // 4 | // This file is part of the DTW package. 5 | // 6 | // DTW is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // DTW is distributed in the hope that it will be useful, but WITHOUT 12 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | // License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with DTW. If not, see . 18 | // 19 | 20 | 21 | // If you copy this algorithm, please cite doi:10.18637/jss.v031.i07 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | // Define R-like functions - Memory alloc'd by R_alloc is automatically freed 29 | #ifdef DTW_R 30 | #include 31 | #define dtw_alloc(n,size) (R_alloc(n,size)) 32 | #else 33 | // Either standalone or Python 34 | #include 35 | #define R_NaInt INT_MIN 36 | #if _WIN32 37 | #include 38 | #define dtw_alloc(n,size) (_alloca((n)*(size))) 39 | #else 40 | #define dtw_alloc(n,size) (alloca((n)*(size))) 41 | #endif 42 | #define error(...) { fprintf (stderr, __VA_ARGS__); exit(-1); } 43 | #endif 44 | 45 | 46 | 47 | #ifndef NAN 48 | #error "This code requires native IEEE NAN support. Verify you are using gcc with -std=gnu99 or recent compilers." 49 | #endif 50 | 51 | 52 | /* undo R indexing */ 53 | #define EP(ii,jj) ((jj)*nsteps+(ii)) 54 | #define EM(ii,jj) ((jj)*n+(ii)) 55 | 56 | #define CLEARCLIST { \ 57 | for(int z=0; z=nsteps) { 134 | error("Error on pattern row %d, pattern number %d out of bounds\n", 135 | i,pn[i]+1); 136 | } 137 | } 138 | 139 | /* assuming pattern ids are in ascending order */ 140 | int npats=pn[nsteps-1]+1; 141 | 142 | /* prepare a cost list per pattern */ 143 | double *clist=(double*) 144 | dtw_alloc((size_t)npats,sizeof(double)); 145 | 146 | /* we do not initialize the seed - the caller is supposed 147 | to do so 148 | cm[0]=lm[0]; 149 | */ 150 | 151 | /* clear the direction matrix */ 152 | for(int i=0; i=0 && jj>=0) { /* address ok? C convention */ 175 | double cc=sc[s]; 176 | if(cc==-1.0) { 177 | clist[p]=cm[EM(ii,jj)]; 178 | } else { /* we rely on NAN to propagate */ 179 | clist[p] += cc*lm[EM(ii,jj)]; 180 | } 181 | } 182 | } 183 | 184 | int minc=argmin(clist,npats); 185 | if(minc>-1) { 186 | cm[EM(i,j)]=clist[minc]; 187 | sm[EM(i,j)]=minc+1; /* convert to 1-based */ 188 | } 189 | } 190 | } 191 | } 192 | 193 | 194 | 195 | 196 | 197 | /* Test as follows: 198 | 199 | R CMD SHLIB -d computeCM.c 200 | 201 | dyn.load("computeCM.so") 202 | lm <- matrix(nrow = 6, ncol = 6, byrow = TRUE, c( 203 | 1, 1, 2, 2, 3, 3, 204 | 1, 1, 1, 2, 2, 2, 205 | 3, 1, 2, 2, 3, 3, 206 | 3, 1, 2, 1, 1, 2, 207 | 3, 2, 1, 2, 1, 2, 208 | 3, 3, 3, 2, 1, 2 209 | )) 210 | step.matrix <- as.matrix(structure(c(1, 1, 2, 2, 3, 3, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 2, 211 | 0, -1, 1, -1, 1, -1, 1), .Dim = c(6L, 4L), class = "stepPattern", npat = 3, norm = "N")) 212 | nsteps<-dim(step.matrix)[1] 213 | wm <- matrix(TRUE,6,6) 214 | cm <- matrix(NA,6,6) 215 | cm[1,1] <- lm[1,1]; 216 | sm <- matrix(NA,6,6) 217 | 218 | out<-.C("computeCM",NAOK=TRUE, 219 | as.integer(dim(cm)), 220 | as.logical(wm), 221 | as.double(lm), 222 | as.integer(nsteps), 223 | as.double(step.matrix), 224 | cmo=as.double(cm), 225 | smo=as.integer(sm)) 226 | 227 | cmoo<-matrix(out$cmo,6,6) 228 | smoo<-matrix(out$smo,6,6) 229 | 230 | storage.mode(wm) <- "logical" 231 | storage.mode(lm) <- "double" 232 | storage.mode(cm) <- "double" 233 | storage.mode(step.matrix) <- "double" 234 | 235 | out2<-.Call("computeCM_Call", wm, lm, cm, step.matrix) 236 | 237 | */ 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | #ifdef TEST_UNIT 247 | /* -------------------------------------------------- 248 | * Unit test - for debugging 249 | */ 250 | 251 | 252 | 253 | 254 | /* 255 | * Printout a matrix. 256 | * int *s: s[0] - no. of rows 257 | * s[1] - no. of columns 258 | * double *mm: matrix to print 259 | * double *r: return value 260 | */ 261 | 262 | void tm_print(int *s, double *mm, double *r) 263 | { 264 | int i,j; 265 | int n=s[0],m=s[1]; 266 | FILE *f=stdout; 267 | 268 | for(i=0; imyg2 287 | */ 288 | 289 | #define TS 5000 290 | #define TSS (TS*TS) 291 | 292 | void test_computeCM() 293 | { 294 | int ts[]= {TS,TS}; 295 | int *twm; 296 | double *tlm; 297 | int tnstepsp[]= {6}; 298 | double tdir[]= {1, 1, 2, 2, 3, 3, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,-1, 1,-1, 1,-1, 1}; 299 | double *tcm; 300 | int *tsm; 301 | 302 | int i,j; 303 | 304 | twm=malloc(TSS*sizeof(int)); 305 | for( i=0; i. 18 | // 19 | 20 | 21 | 22 | #ifndef _DTW_CORE_H 23 | #define _DTW_CORE_H 24 | 25 | void computeCM( /* IN */ 26 | const int *s, /* mtrx dimensions, int */ 27 | const int *wm, /* windowing matrix, logical=int */ 28 | const double *lm, /* local cost mtrx, numeric */ 29 | const int *nstepsp, /* no of steps in stepPattern, int */ 30 | const double *dir, /* stepPattern description, numeric */ 31 | /* IN+OUT */ 32 | double *cm, /* cost matrix, numeric */ 33 | /* OUT */ 34 | int *sm /* direction mtrx, int */ 35 | ) ; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /dtw/dtw_test_data.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """Miscellaneous test data for DTW""" 21 | 22 | 23 | import numpy 24 | from pkgutil import get_data 25 | 26 | def aami(): 27 | # IMPORT_RDOCSTRING aami 28 | """ANSI/AAMI EC13 Test Waveforms, 3a and 3b 29 | 30 | ANSI/AAMI EC13 Test Waveforms 3a and 3b, as obtained from the PhysioBank 31 | database. 32 | 33 | **Details** 34 | 35 | The following text is reproduced (abridged) from PhysioBank, page 36 | https://www_physionet_org/content/aami-ec13/1.0.0/. Other recordings 37 | belong to the dataset and can be obtained from the same page. 38 | 39 | The files in this set can be used for testing a variety of devices that 40 | monitor the electrocardiogram. The recordings include both synthetic and 41 | real waveforms. For details on these test waveforms and how to use them, 42 | please refer to section 5.1.2.1, paragraphs (e) and (g) in the reference 43 | below. Each recording contains one ECG signal sampled at 720 Hz with 44 | 12-bit resolution. 45 | 46 | Notes 47 | ----- 48 | 49 | Timestamps in the datasets have been re-created at the indicated 50 | frequency of 720 Hz, whereas the original timestamps in ms (at least in 51 | text format) only had three decimal digits’ precision, and were 52 | therefore affected by substantial jittering. 53 | 54 | References 55 | ---------- 56 | 57 | - Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark 58 | RG, Mietus JE, Moody GB, Peng CK, Stanley HE. *PhysioBank, 59 | PhysioToolkit, and PhysioNet: Components of a New Research Resource 60 | for Complex Physiologic Signals.* Circulation 101(23):e215-e220; 2000 61 | (June 13). 62 | - Cardiac monitors, heart rate meters, and alarms; American National 63 | Standard (ANSI/AAMI EC13:2002). Arlington, VA: Association for the 64 | Advancement of Medical Instrumentation, 2002. 65 | 66 | Examples 67 | -------- 68 | >>> from dtw import *; import numpy as np 69 | >>> (aami3a, aami3b) = dtw_test_data.aami() 70 | 71 | Timestamps (ms) are in the first row, values (mV) in the second. 72 | 73 | >>> with np.printoptions(precision=3): print(aami3a[0,:]) # doctest: +NORMALIZE_WHITESPACE 74 | [0.000e+00 1.389e+00 2.778e+00 ... 5.983e+04 5.983e+04 5.983e+04] 75 | 76 | >>> with np.printoptions(precision=3): print(aami3a[1,:]) # doctest: +NORMALIZE_WHITESPACE 77 | [0.185 0.185 0.169 ... 0.208 0.208 0.208] 78 | 79 | """ 80 | # ENDIMPORT 81 | 82 | ts = lambda v: numpy.arange(len(v))/720.*1000. 83 | a3a = numpy.fromstring(get_data(__name__, 'data/aami3a.csv'), sep="\n") 84 | a3b = numpy.fromstring(get_data(__name__, 'data/aami3b.csv'), sep="\n") 85 | 86 | aami3a = numpy.vstack([ts(a3a),a3a]) 87 | aami3b = numpy.vstack([ts(a3b),a3b]) 88 | 89 | return (aami3a, aami3b) 90 | 91 | 92 | 93 | def sin_cos(): 94 | """Noisy sine vs cosine demo data. 95 | 96 | Returns a tuple (query, reference) used in various examples, defined 97 | as follows: 98 | 99 | _idx = numpy.linspace(0,6.28,num=100) 100 | query = numpy.sin(_idx) + numpy.random.uniform(0,0.1,len(_idx)), 101 | reference = numpy.cos(_idx) 102 | 103 | """ 104 | _idx = numpy.linspace(0,6.28,num=100) 105 | return ( 106 | numpy.sin(_idx) + numpy.random.uniform(0,0.1,len(_idx)), 107 | numpy.cos(_idx) 108 | ) 109 | -------------------------------------------------------------------------------- /dtw/mvm.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """Minimum Variance Matching pattern""" 21 | 22 | 23 | from dtw.stepPattern import StepPattern 24 | import numpy 25 | 26 | def mvmStepPattern(elasticity=20): 27 | # IMPORT_RDOCSTRING mvmStepPattern 28 | """Minimum Variance Matching algorithm 29 | 30 | Step patterns to compute the Minimum Variance Matching (MVM) 31 | correspondence between time series 32 | 33 | **Details** 34 | 35 | The Minimum Variance Matching algorithm (1) finds the non-contiguous 36 | parts of reference which best match the query, allowing for arbitrarily 37 | long “stretches” of reference to be excluded from the match. All 38 | elements of the query have to be matched. First and last elements of the 39 | query are anchored at the boundaries of the reference. 40 | 41 | The ``mvmStepPattern`` function creates a ``stepPattern`` object which 42 | implements this behavior, to be used with the usual [dtw()] call (see 43 | example). MVM is computed as a special case of DTW, with a very large, 44 | asymmetric-like step pattern. 45 | 46 | The ``elasticity`` argument limits the maximum run length of reference 47 | which can be skipped at once. If no limit is desired, set ``elasticity`` 48 | to an integer at least as large as the reference (computation time grows 49 | linearly). 50 | 51 | Parameters 52 | ---------- 53 | elasticity : 54 | integer: maximum consecutive reference elements skippable 55 | 56 | Returns 57 | ------- 58 | 59 | A step pattern object. 60 | 61 | References 62 | ---------- 63 | 64 | Latecki, L. J.; Megalooikonomou, V.; Wang, Q. & Yu, D. *An elastic 65 | partial shape matching technique* Pattern Recognition, 2007, 40, 66 | 3069-3080. 67 | `doi:10.1016/j_patcog.2007.03.004 `__ 68 | 69 | Examples 70 | -------- 71 | 72 | >>> import numpy as np 73 | >>> from dtw import * 74 | 75 | The hand-checkable example given in Fig. 5, ref. [1] above 76 | 77 | >>> diffmx = np.array( 78 | ... [[ 0, 1, 8, 2, 2, 4, 8 ], 79 | ... [ 1, 0, 7, 1, 1, 3, 7 ], 80 | ... [ -7, -6, 1, -5, -5, -3, 1 ], 81 | ... [ -5, -4, 3, -3, -3, -1, 3 ], 82 | ... [ -7, -6, 1, -5, -5, -3, 1 ]], dtype=np.double ) 83 | 84 | Cost matrix 85 | 86 | >>> costmx = diffmx**2; 87 | 88 | Compute the alignment 89 | 90 | >>> al = dtw(costmx,step_pattern=mvmStepPattern(10)) 91 | 92 | Elements 4,5 are skipped 93 | 94 | >>> al.index2+1 95 | array([1, 2, 3, 6, 7]) 96 | 97 | >>> al.plot() # doctest: +SKIP 98 | 99 | """ 100 | # ENDIMPORT 101 | 102 | size = elasticity 103 | 104 | pn = numpy.repeat(numpy.arange(size) + 1, 2) 105 | dx = numpy.tile([1, 0], size) 106 | dy = pn * dx 107 | w = numpy.tile([-1, 1], size) 108 | 109 | tmp = numpy.vstack([pn, dx, dy, w]).T 110 | 111 | return StepPattern(tmp, "N") 112 | -------------------------------------------------------------------------------- /dtw/stepPattern.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """Step Pattern handling 21 | 22 | See documentation for the StepPattern class. 23 | """ 24 | 25 | import numpy 26 | 27 | 28 | class StepPattern: 29 | # IMPORT_RDOCSTRING stepPattern 30 | """Step patterns for DTW 31 | 32 | A ``stepPattern`` object lists the transitions allowed while searching 33 | for the minimum-distance path. DTW variants are implemented by passing 34 | one of the objects described in this page to the ``stepPattern`` 35 | argument of the [dtw()] call. 36 | 37 | **Details** 38 | 39 | A step pattern characterizes the matching model and slope constraint 40 | specific of a DTW variant. They also known as local- or 41 | slope-constraints, transition types, production or recursion rules 42 | (GiorginoJSS). 43 | 44 | **Pre-defined step patterns** 45 | 46 | :: 47 | 48 | ## Well-known step patterns 49 | symmetric1 50 | symmetric2 51 | asymmetric 52 | 53 | ## Step patterns classified according to Rabiner-Juang (Rabiner1993) 54 | rabinerJuangStepPattern(type,slope_weighting="d",smoothed=False) 55 | 56 | ## Slope-constrained step patterns from Sakoe-Chiba (Sakoe1978) 57 | symmetricP0; asymmetricP0 58 | symmetricP05; asymmetricP05 59 | symmetricP1; asymmetricP1 60 | symmetricP2; asymmetricP2 61 | 62 | ## Step patterns classified according to Rabiner-Myers (Myers1980) 63 | typeIa; typeIb; typeIc; typeId; 64 | typeIas; typeIbs; typeIcs; typeIds; # smoothed 65 | typeIIa; typeIIb; typeIIc; typeIId; 66 | typeIIIc; typeIVc; 67 | 68 | ## Miscellaneous 69 | mori2006; 70 | rigid; 71 | 72 | A variety of classification schemes have been proposed for step 73 | patterns, including Sakoe-Chiba (Sakoe1978); Rabiner-Juang 74 | (Rabiner1993); and Rabiner-Myers (Myers1980). The ``dtw`` package 75 | implements all of the transition types found in those papers, with the 76 | exception of Itakura’s and Velichko-Zagoruyko’s steps, which require 77 | subtly different algorithms (this may be rectified in the future). 78 | Itakura recursion is almost, but not quite, equivalent to ``typeIIIc``. 79 | 80 | For convenience, we shall review pre-defined step patterns grouped by 81 | classification. Note that the same pattern may be listed under different 82 | names. Refer to paper (GiorginoJSS) for full details. 83 | 84 | **1. Well-known step patterns** 85 | 86 | Common DTW implementations are based on one of the following transition 87 | types. 88 | 89 | ``symmetric2`` is the normalizable, symmetric, with no local slope 90 | constraints. Since one diagonal step costs as much as the two equivalent 91 | steps along the sides, it can be normalized dividing by ``N+M`` 92 | (query+reference lengths). It is widely used and the default. 93 | 94 | ``asymmetric`` is asymmetric, slope constrained between 0 and 2. Matches 95 | each element of the query time series exactly once, so the warping path 96 | ``index2~index1`` is guaranteed to be single-valued. Normalized by ``N`` 97 | (length of query). 98 | 99 | ``symmetric1`` (or White-Neely) is quasi-symmetric, no local constraint, 100 | non-normalizable. It is biased in favor of oblique steps. 101 | 102 | **2. The Rabiner-Juang set** 103 | 104 | A comprehensive table of step patterns is proposed in Rabiner-Juang’s 105 | book (Rabiner1993), tab. 4.5. All of them can be constructed through the 106 | ``rabinerJuangStepPattern(type,slope_weighting,smoothed)`` function. 107 | 108 | The classification foresees seven families, labelled with Roman numerals 109 | I-VII; here, they are selected through the integer argument ``type``. 110 | Each family has four slope weighting sub-types, named in sec. 4.7.2.5 as 111 | “Type (a)” to “Type (d)”; they are selected passing a character argument 112 | ``slope_weighting``, as in the table below. Furthermore, each subtype 113 | can be either plain or smoothed (figure 4.44); smoothing is enabled 114 | setting the logical argument ``smoothed``. (Not all combinations of 115 | arguments make sense.) 116 | 117 | :: 118 | 119 | Subtype | Rule | Norm | Unbiased 120 | --------|------------|------|--------- 121 | a | min step | -- | NO 122 | b | max step | -- | NO 123 | c | Di step | N | YES 124 | d | Di+Dj step | N+M | YES 125 | 126 | **3. The Sakoe-Chiba set** 127 | 128 | Sakoe-Chiba (Sakoe1978) discuss a family of slope-constrained patterns; 129 | they are implemented as shown in page 47, table I. Here, they are called 130 | ``symmetricP`` and ``asymmetricP``, where ```` corresponds to 131 | Sakoe’s integer slope parameter *P*. Values available are accordingly: 132 | ``0`` (no constraint), ``1``, ``05`` (one half) and ``2``. See 133 | (Sakoe1978) for details. 134 | 135 | **4. The Rabiner-Myers set** 136 | 137 | The ``type`` step patterns follow the older Rabiner-Myers’ 138 | classification proposed in (Myers1980) and (MRR1980). Note that this is 139 | a subset of the Rabiner-Juang set (Rabiner1993), and the latter should 140 | be preferred in order to avoid confusion. ```` is a Roman numeral 141 | specifying the shape of the transitions; ```` is a letter in the 142 | range ``a-d`` specifying the weighting used per step, as above; 143 | ``typeIIx`` patterns also have a version ending in ``s``, meaning the 144 | smoothing is used (which does not permit skipping points). The 145 | ``typeId, typeIId`` and ``typeIIds`` are unbiased and symmetric. 146 | 147 | **5. Others** 148 | 149 | The ``rigid`` pattern enforces a fixed unitary slope. It only makes 150 | sense in combination with ``open_begin=True``, ``open_end=True`` to find 151 | gapless subsequences. It may be seen as the ``P->inf`` limiting case in 152 | Sakoe’s classification. 153 | 154 | ``mori2006`` is Mori’s asymmetric step-constrained pattern (Mori2006). 155 | It is normalized by the matched reference length. 156 | 157 | [mvmStepPattern()] implements Latecki’s Minimum Variance Matching 158 | algorithm, and it is described in its own page. 159 | 160 | **Methods** 161 | 162 | ``print_stepPattern`` prints an user-readable description of the 163 | recurrence equation defined by the given pattern. 164 | 165 | ``plot_stepPattern`` graphically displays the step patterns productions 166 | which can lead to element (0,0). Weights are shown along the step 167 | leading to the corresponding element. 168 | 169 | ``t_stepPattern`` transposes the productions and normalization hint so 170 | that roles of query and reference become reversed. 171 | 172 | Parameters 173 | ---------- 174 | x : 175 | a step pattern object 176 | type : 177 | path specification, integer 1..7 (see (Rabiner1993), table 4.5) 178 | slope_weighting : 179 | slope weighting rule: character `"a"` to `"d"` (see (Rabiner1993), sec. 4.7.2.5) 180 | smoothed : 181 | logical, whether to use smoothing (see (Rabiner1993), fig. 4.44) 182 | ... : 183 | additional arguments to [print()]. 184 | 185 | Notes 186 | ----- 187 | 188 | Constructing ``stepPattern`` objects is tricky and thus undocumented. 189 | For a commented example please see source code for ``symmetricP1``. 190 | 191 | References 192 | ---------- 193 | 194 | - (GiorginoJSS) Toni Giorgino. *Computing and Visualizing Dynamic Time 195 | Warping Alignments in R: The dtw Package.* Journal of Statistical 196 | Software, 31(7), 1-24. 197 | `doi:10.18637/jss_v031.i07 `__ 198 | - (Itakura1975) Itakura, F., *Minimum prediction residual principle 199 | applied to speech recognition,* Acoustics, Speech, and Signal 200 | Processing, IEEE Transactions on , vol.23, no.1, pp. 67-72, Feb 1975. 201 | `doi:10.1109/TASSP.1975.1162641 `__ 202 | - (MRR1980) Myers, C.; Rabiner, L. & Rosenberg, A. *Performance 203 | tradeoffs in dynamic time warping algorithms for isolated word 204 | recognition*, IEEE Trans. Acoust., Speech, Signal Process., 1980, 28, 205 | 623-635. 206 | `doi:10.1109/TASSP.1980.1163491 `__ 207 | - (Mori2006) Mori, A.; Uchida, S.; Kurazume, R.; Taniguchi, R.; 208 | Hasegawa, T. & Sakoe, H. Early Recognition and Prediction of Gestures 209 | Proc. 18th International Conference on Pattern Recognition ICPR 2006, 210 | 2006, 3, 560-563. 211 | `doi:10.1109/ICPR.2006.467 `__ 212 | - (Myers1980) Myers, Cory S. *A Comparative Study Of Several Dynamic 213 | Time Warping Algorithms For Speech Recognition*, MS and BS thesis, 214 | Dept. of Electrical Engineering and Computer Science, Massachusetts 215 | Institute of Technology, archived Jun 20 1980, 216 | https://hdl_handle_net/1721.1/27909 217 | - (Rabiner1993) Rabiner, L. R., & Juang, B.-H. (1993). *Fundamentals of 218 | speech recognition.* Englewood Cliffs, NJ: Prentice Hall. 219 | - (Sakoe1978) Sakoe, H.; Chiba, S., *Dynamic programming algorithm 220 | optimization for spoken word recognition,* Acoustics, Speech, and 221 | Signal Processing, IEEE Transactions on , vol.26, no.1, pp. 43-49, 222 | Feb 1978 223 | `doi:10.1109/TASSP.1978.1163055 `__ 224 | 225 | Examples 226 | -------- 227 | >>> from dtw import * 228 | >>> import numpy as np 229 | 230 | The usual (normalizable) symmetric step pattern 231 | Step pattern recursion, defined as: 232 | g[i,j] = min( 233 | g[i,j-1] + d[i,j] , 234 | g[i-1,j-1] + 2 * d[i,j] , 235 | g[i-1,j] + d[i,j] , 236 | ) 237 | 238 | >>> print(symmetric2) #doctest: +NORMALIZE_WHITESPACE 239 | Step pattern recursion: 240 | g[i,j] = min( 241 | g[i-1,j-1] + 2 * d[i ,j ] , 242 | g[i ,j-1] + d[i ,j ] , 243 | g[i-1,j ] + d[i ,j ] , 244 | ) 245 | 246 | Normalization hint: N+M 247 | 248 | 249 | The well-known plotting style for step patterns 250 | 251 | >>> import matplotlib.pyplot as plt; # doctest: +SKIP 252 | ... symmetricP2.plot().set_title("Sakoe's Symmetric P=2 recursion") 253 | 254 | Same example seen in ?dtw , now with asymmetric step pattern 255 | 256 | >>> (query, reference) = dtw_test_data.sin_cos() 257 | 258 | Do the computation 259 | 260 | >>> asy = dtw(query, reference, keep_internals=True, 261 | ... step_pattern=asymmetric); 262 | 263 | >>> dtwPlot(asy,type="density" # doctest: +SKIP 264 | ... ).set_title("Sine and cosine, asymmetric step") 265 | 266 | Hand-checkable example given in [Myers1980] p 61 - see JSS paper 267 | 268 | >>> tm = numpy.reshape( [1, 3, 4, 4, 5, 2, 2, 3, 3, 4, 3, 1, 1, 1, 3, 4, 2, 269 | ... 3, 3, 2, 5, 3, 4, 4, 1], (5,5) ) 270 | 271 | """ 272 | # ENDIMPORT 273 | 274 | def __init__(self, mx, hint="NA"): 275 | self.mx = numpy.array(mx, dtype=numpy.double) 276 | self.hint = hint 277 | 278 | def get_n_rows(self): 279 | """Total number of steps in the recursion.""" 280 | return self.mx.shape[0] 281 | 282 | def get_n_patterns(self): 283 | """Number of rules in the recursion.""" 284 | return int(numpy.max(self.mx[:, 0])) 285 | 286 | def T(self): 287 | """Transpose a step pattern.""" 288 | tmx = self.mx.copy() 289 | tmx = tmx[:, [0, 2, 1, 3]] 290 | th = self.hint 291 | if th == "N": 292 | th = "M" 293 | elif th == "M": 294 | th = "N" 295 | tsp = StepPattern(tmx, th) 296 | return tsp 297 | 298 | def __str__(self): 299 | np = self.get_n_patterns() 300 | head = " g[i,j] = min(\n" 301 | 302 | body = "" 303 | for p in range(1, np + 1): 304 | steps = self._extractpattern(p) 305 | ns = steps.shape[0] 306 | steps = numpy.flip(steps, 0) 307 | 308 | for s in range(ns): 309 | di, dj, cc = steps[s, :] 310 | dis = "" if di == 0 else f"{-int(di)}" 311 | djs = "" if dj == 0 else f"{-int(dj)}" 312 | dijs = f"i{dis:2},j{djs:2}" 313 | 314 | if cc == -1: 315 | gs = f" g[{dijs}]" 316 | body = body + " " + gs 317 | else: 318 | ccs = " " if cc == 1 else f"{cc:2.2g} *" 319 | ds = f"+{ccs} d[{dijs}]" 320 | body = body + " " + ds 321 | body = body + " ,\n" 322 | 323 | tail = " ) \n\n" 324 | ntxt = f"Normalization hint: {self.hint}\n" 325 | 326 | return "Step pattern recursion:\n" + head + body + tail + ntxt 327 | 328 | def plot(self): 329 | """Provide a visual description of a StepPattern object""" 330 | import matplotlib.pyplot as plt 331 | x = self.mx 332 | pats = numpy.arange(1, 1 + self.get_n_patterns()) 333 | 334 | alpha = .5 335 | fudge = [0, 0] 336 | 337 | fig, ax = plt.subplots(figsize=(6, 6)) 338 | for i in pats: 339 | ss = x[:, 0] == i 340 | ax.plot(-x[ss, 1], -x[ss, 2], lw=2, color="tab:blue") 341 | ax.plot(-x[ss, 1], -x[ss, 2], 'o', color="black", marker="o", fillstyle="none") 342 | 343 | if numpy.sum(ss) == 1: continue 344 | 345 | xss = x[ss, :] 346 | xh = alpha * xss[:-1, 1] + (1 - alpha) * xss[1:, 1] 347 | yh = alpha * xss[:-1, 2] + (1 - alpha) * xss[1:, 2] 348 | 349 | for xx, yy, tt in zip(xh, yh, xss[1:, 3]): 350 | ax.annotate("{:.2g}".format(tt), (-xx - fudge[0], 351 | -yy - fudge[1])) 352 | 353 | endpts = x[:, 3] == -1 354 | ax.plot(-x[endpts, 1], -x[endpts, 2], 'o', color="black") 355 | 356 | ax.set_xlabel("Query index") 357 | ax.set_ylabel("Reference index") 358 | ax.set_xticks(numpy.unique(-x[:, 1])) 359 | ax.set_yticks(numpy.unique(-x[:, 2])) 360 | return ax 361 | 362 | def _extractpattern(self, sn): 363 | sp = self.mx 364 | sbs = sp[:, 0] == sn 365 | spl = sp[sbs, 1:] 366 | return numpy.flip(spl, 0) 367 | 368 | def _mkDirDeltas(self): 369 | out = numpy.array(self.mx, dtype=numpy.int32) 370 | out = out[out[:, 3] == -1, :] 371 | out = out[:, [1, 2]] 372 | return out 373 | 374 | def _get_p(self): 375 | # Dimensions are reversed wrt R 376 | s = self.mx[:, [0, 2, 1, 3]] 377 | return s.T.reshape(-1) 378 | 379 | 380 | 381 | # Alternate constructor for ease of R import 382 | def _c(*v): 383 | va = numpy.array([*v]) 384 | if len(va) % 4 != 0: 385 | _error("Internal error in _c constructor") 386 | va = va.reshape((-1, 4)) 387 | return (va) 388 | 389 | 390 | # Kludge because lambda: raise doesn't work 391 | def _error(s): 392 | raise ValueError(s) 393 | 394 | 395 | ################################################## 396 | ################################################## 397 | 398 | # Reimplementation of the building process 399 | 400 | class _P: 401 | def __init__(self, pid, subtype, smoothing): 402 | self.subtype = subtype 403 | self.smoothing = smoothing 404 | self.pid = pid 405 | self.i = [0] 406 | self.j = [0] 407 | 408 | def step(self, di, dj): # equivalent to .Pstep 409 | self.i.append(di) 410 | self.j.append(dj) 411 | return self 412 | 413 | def get(self): # eqv to .Pend 414 | ia = numpy.array(self.i, dtype=numpy.double) 415 | ja = numpy.array(self.j, dtype=numpy.double) 416 | si = numpy.cumsum(ia) 417 | sj = numpy.cumsum(ja) 418 | ni = numpy.max(si) - si # ? 419 | nj = numpy.max(sj) - sj 420 | if self.subtype == "a": 421 | w = numpy.minimum(ia, ja) 422 | elif self.subtype == "b": 423 | w = numpy.maximum(ia, ja) 424 | elif self.subtype == "c": 425 | w = ia 426 | elif self.subtype == "d": 427 | w = ia + ja 428 | else: 429 | _error("Unsupported subtype") 430 | 431 | if self.smoothing: 432 | # if self.pid==3: import ipdb; ipdb.set_trace() 433 | w[1:] = numpy.mean(w[1:]) 434 | 435 | w[0] = -1.0 436 | 437 | nr = len(w) 438 | mx = numpy.zeros((nr, 4)) 439 | mx[:, 0] = self.pid 440 | mx[:, 1] = ni 441 | mx[:, 2] = nj 442 | mx[:, 3] = w 443 | return mx 444 | 445 | 446 | def rabinerJuangStepPattern(ptype, slope_weighting="d", smoothed=False): 447 | """Construct a pattern classified according to the Rabiner-Juang scheme (Rabiner1993) 448 | 449 | See documentation for the StepPattern class. 450 | """ 451 | 452 | f = { 453 | 1: _RJtypeI, 454 | 2: _RJtypeII, 455 | 3: _RJtypeIII, 456 | 4: _RJtypeIV, 457 | 5: _RJtypeV, 458 | 6: _RJtypeVI, 459 | 7: _RJtypeVII 460 | }.get(ptype, lambda: _error("Invalid type")) 461 | 462 | r = f(slope_weighting, smoothed) 463 | norm = "NA" 464 | if slope_weighting == "c": 465 | norm = "N" 466 | elif slope_weighting == "d": 467 | norm = "N+M" 468 | 469 | return StepPattern(r, norm) 470 | 471 | 472 | def _RJtypeI(s, m): 473 | return numpy.vstack([ 474 | _P(1, s, m).step(1, 0).get(), 475 | _P(2, s, m).step(1, 1).get(), 476 | _P(3, s, m).step(0, 1).get()]) 477 | 478 | 479 | def _RJtypeII(s, m): 480 | return numpy.vstack([ 481 | _P(1, s, m).step(1, 1).step(1, 0).get(), 482 | _P(2, s, m).step(1, 1).get(), 483 | _P(3, s, m).step(1, 1).step(0, 1).get()]) 484 | 485 | 486 | def _RJtypeIII(s, m): 487 | return numpy.vstack([ 488 | _P(1, s, m).step(2, 1).get(), 489 | _P(2, s, m).step(1, 1).get(), 490 | _P(3, s, m).step(1, 2).get()]) 491 | 492 | 493 | def _RJtypeIV(s, m): 494 | return numpy.vstack([ 495 | _P(1, s, m).step(1, 1).step(1, 0).get(), 496 | _P(2, s, m).step(1, 2).step(1, 0).get(), 497 | _P(3, s, m).step(1, 1).get(), 498 | _P(4, s, m).step(1, 2).get(), 499 | ]) 500 | 501 | 502 | def _RJtypeV(s, m): 503 | return numpy.vstack([ 504 | _P(1, s, m).step(1, 1).step(1, 0).step(1, 0).get(), 505 | _P(2, s, m).step(1, 1).step(1, 0).get(), 506 | _P(3, s, m).step(1, 1).get(), 507 | _P(4, s, m).step(1, 1).step(0, 1).get(), 508 | _P(5, s, m).step(1, 1).step(0, 1).step(0, 1).get(), 509 | ]) 510 | 511 | 512 | def _RJtypeVI(s, m): 513 | return numpy.vstack([ 514 | _P(1, s, m).step(1, 1).step(1, 1).step(1, 0).get(), 515 | _P(2, s, m).step(1, 1).get(), 516 | _P(3, s, m).step(1, 1).step(1, 1).step(0, 1).get() 517 | ]) 518 | 519 | 520 | def _RJtypeVII(s, m): 521 | return numpy.vstack([ 522 | _P(1, s, m).step(1, 1).step(1, 0).step(1, 0).get(), 523 | _P(2, s, m).step(1, 2).step(1, 0).step(1, 0).get(), 524 | _P(3, s, m).step(1, 3).step(1, 0).step(1, 0).get(), 525 | _P(4, s, m).step(1, 1).step(1, 0).get(), 526 | _P(5, s, m).step(1, 2).step(1, 0).get(), 527 | _P(6, s, m).step(1, 3).step(1, 0).get(), 528 | _P(7, s, m).step(1, 1).get(), 529 | _P(8, s, m).step(1, 2).get(), 530 | _P(9, s, m).step(1, 3).get(), 531 | ]) 532 | 533 | 534 | ########################################################################################## 535 | ########################################################################################## 536 | 537 | ## Everything here is semi auto-generated from the R source. Don't 538 | ## edit! 539 | 540 | 541 | ################################################## 542 | ################################################## 543 | 544 | 545 | ## 546 | ## Various step patterns, defined as internal variables 547 | ## 548 | ## First column: enumerates step patterns. 549 | ## Second step in query index 550 | ## Third step in reference index 551 | ## Fourth weight if positive, or -1 if starting point 552 | ## 553 | ## For \cite{} see dtw.bib in the package 554 | ## 555 | 556 | 557 | ## Widely-known variants 558 | 559 | ## White-Neely symmetric (default) 560 | ## aka Quasi-symmetric \cite{White1976} 561 | ## normalization: no (N+M?) 562 | symmetric1 = StepPattern(_c( 563 | 1, 1, 1, -1, 564 | 1, 0, 0, 1, 565 | 2, 0, 1, -1, 566 | 2, 0, 0, 1, 567 | 3, 1, 0, -1, 568 | 3, 0, 0, 1 569 | )) 570 | 571 | ## Normal symmetric 572 | ## normalization: N+M 573 | symmetric2 = StepPattern(_c( 574 | 1, 1, 1, -1, 575 | 1, 0, 0, 2, 576 | 2, 0, 1, -1, 577 | 2, 0, 0, 1, 578 | 3, 1, 0, -1, 579 | 3, 0, 0, 1 580 | ), "N+M") 581 | 582 | ## classic asymmetric pattern: max slope 2, min slope 0 583 | ## normalization: N 584 | asymmetric = StepPattern(_c( 585 | 1, 1, 0, -1, 586 | 1, 0, 0, 1, 587 | 2, 1, 1, -1, 588 | 2, 0, 0, 1, 589 | 3, 1, 2, -1, 590 | 3, 0, 0, 1 591 | ), "N") 592 | 593 | # % \item{\code{symmetricVelichkoZagoruyko}}{symmetric, reproduced from % 594 | # [Sakoe1978]. Use distance matrix \code{1-d}} 595 | # 596 | 597 | ## normalization: max[N,M] 598 | ## note: local distance matrix is 1-d 599 | ## \cite{Velichko} 600 | _symmetricVelichkoZagoruyko = StepPattern(_c( 601 | 1, 0, 1, -1, 602 | 2, 1, 1, -1, 603 | 2, 0, 0, -1.001, 604 | 3, 1, 0, -1)) 605 | 606 | # % \item{\code{asymmetricItakura}}{asymmetric, slope contrained 0.5 -- 2 607 | # from reference [Itakura1975]. This is the recursive definition % that 608 | # generates the Itakura parallelogram; } 609 | # 610 | 611 | ## Itakura slope-limited asymmetric \cite{Itakura1975} 612 | ## Max slope: 2; min slope: 1/2 613 | ## normalization: N 614 | _asymmetricItakura = StepPattern(_c( 615 | 1, 1, 2, -1, 616 | 1, 0, 0, 1, 617 | 2, 1, 1, -1, 618 | 2, 0, 0, 1, 619 | 3, 2, 1, -1, 620 | 3, 1, 0, 1, 621 | 3, 0, 0, 1, 622 | 4, 2, 2, -1, 623 | 4, 1, 0, 1, 624 | 4, 0, 0, 1 625 | )) 626 | 627 | ############################# 628 | ## Slope-limited versions 629 | ## 630 | ## Taken from Table I, page 47 of "Dynamic programming algorithm 631 | ## optimization for spoken word recognition," Acoustics, Speech, and 632 | ## Signal Processing, vol.26, no.1, pp. 43-49, Feb 1978 URL: 633 | ## http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=1163055 634 | ## 635 | ## Mostly unchecked 636 | 637 | 638 | ## Row P=0 639 | symmetricP0 = symmetric2 640 | 641 | ## normalization: N ? 642 | asymmetricP0 = StepPattern(_c( 643 | 1, 0, 1, -1, 644 | 1, 0, 0, 0, 645 | 2, 1, 1, -1, 646 | 2, 0, 0, 1, 647 | 3, 1, 0, -1, 648 | 3, 0, 0, 1 649 | ), "N") 650 | 651 | ## alternative implementation 652 | _asymmetricP0b = StepPattern(_c( 653 | 1, 0, 1, -1, 654 | 2, 1, 1, -1, 655 | 2, 0, 0, 1, 656 | 3, 1, 0, -1, 657 | 3, 0, 0, 1 658 | ), "N") 659 | 660 | ## Row P=1/2 661 | symmetricP05 = StepPattern(_c( 662 | 1, 1, 3, -1, 663 | 1, 0, 2, 2, 664 | 1, 0, 1, 1, 665 | 1, 0, 0, 1, 666 | 2, 1, 2, -1, 667 | 2, 0, 1, 2, 668 | 2, 0, 0, 1, 669 | 3, 1, 1, -1, 670 | 3, 0, 0, 2, 671 | 4, 2, 1, -1, 672 | 4, 1, 0, 2, 673 | 4, 0, 0, 1, 674 | 5, 3, 1, -1, 675 | 5, 2, 0, 2, 676 | 5, 1, 0, 1, 677 | 5, 0, 0, 1 678 | ), "N+M") 679 | 680 | asymmetricP05 = StepPattern(_c( 681 | 1, 1, 3, -1, 682 | 1, 0, 2, 1 / 3, 683 | 1, 0, 1, 1 / 3, 684 | 1, 0, 0, 1 / 3, 685 | 2, 1, 2, -1, 686 | 2, 0, 1, .5, 687 | 2, 0, 0, .5, 688 | 3, 1, 1, -1, 689 | 3, 0, 0, 1, 690 | 4, 2, 1, -1, 691 | 4, 1, 0, 1, 692 | 4, 0, 0, 1, 693 | 5, 3, 1, -1, 694 | 5, 2, 0, 1, 695 | 5, 1, 0, 1, 696 | 5, 0, 0, 1 697 | ), "N") 698 | 699 | ## Row P=1 700 | ## Implementation of Sakoe's P=1, Symmetric algorithm 701 | 702 | symmetricP1 = StepPattern(_c( 703 | 1, 1, 2, -1, # First branch: g(i-1,j-2)+ 704 | 1, 0, 1, 2, # + 2d(i ,j-1) 705 | 1, 0, 0, 1, # + d(i ,j) 706 | 2, 1, 1, -1, # Second branch: g(i-1,j-1)+ 707 | 2, 0, 0, 2, # +2d(i, j) 708 | 3, 2, 1, -1, # Third branch: g(i-2,j-1)+ 709 | 3, 1, 0, 2, # + 2d(i-1,j) 710 | 3, 0, 0, 1 # + d( i,j) 711 | ), "N+M") 712 | 713 | asymmetricP1 = StepPattern(_c( 714 | 1, 1, 2, -1, 715 | 1, 0, 1, .5, 716 | 1, 0, 0, .5, 717 | 2, 1, 1, -1, 718 | 2, 0, 0, 1, 719 | 3, 2, 1, -1, 720 | 3, 1, 0, 1, 721 | 3, 0, 0, 1 722 | ), "N") 723 | 724 | ## Row P=2 725 | symmetricP2 = StepPattern(_c( 726 | 1, 2, 3, -1, 727 | 1, 1, 2, 2, 728 | 1, 0, 1, 2, 729 | 1, 0, 0, 1, 730 | 2, 1, 1, -1, 731 | 2, 0, 0, 2, 732 | 3, 3, 2, -1, 733 | 3, 2, 1, 2, 734 | 3, 1, 0, 2, 735 | 3, 0, 0, 1 736 | ), "N+M") 737 | 738 | asymmetricP2 = StepPattern(_c( 739 | 1, 2, 3, -1, 740 | 1, 1, 2, 2 / 3, 741 | 1, 0, 1, 2 / 3, 742 | 1, 0, 0, 2 / 3, 743 | 2, 1, 1, -1, 744 | 2, 0, 0, 1, 745 | 3, 3, 2, -1, 746 | 3, 2, 1, 1, 747 | 3, 1, 0, 1, 748 | 3, 0, 0, 1 749 | ), "N") 750 | 751 | ################################ 752 | ## Taken from Table III, page 49. 753 | ## Four varieties of DP-algorithm compared 754 | 755 | ## 1st row: asymmetric 756 | 757 | ## 2nd row: symmetricVelichkoZagoruyko 758 | 759 | ## 3rd row: symmetric1 760 | 761 | ## 4th row: asymmetricItakura 762 | 763 | 764 | ############################# 765 | ## Classified according to Rabiner 766 | ## 767 | ## Taken from chapter 2, Myers' thesis [4]. Letter is 768 | ## the weighting function: 769 | ## 770 | ## rule norm unbiased 771 | ## a min step ~N NO 772 | ## b max step ~N NO 773 | ## c x step N YES 774 | ## d x+y step N+M YES 775 | ## 776 | ## Mostly unchecked 777 | 778 | # R-Myers R-Juang 779 | # type I type II 780 | # type II type III 781 | # type III type IV 782 | # type IV type VII 783 | 784 | 785 | typeIa = StepPattern(_c( 786 | 1, 2, 1, -1, 787 | 1, 1, 0, 1, 788 | 1, 0, 0, 0, 789 | 2, 1, 1, -1, 790 | 2, 0, 0, 1, 791 | 3, 1, 2, -1, 792 | 3, 0, 1, 1, 793 | 3, 0, 0, 0 794 | )) 795 | 796 | typeIb = StepPattern(_c( 797 | 1, 2, 1, -1, 798 | 1, 1, 0, 1, 799 | 1, 0, 0, 1, 800 | 2, 1, 1, -1, 801 | 2, 0, 0, 1, 802 | 3, 1, 2, -1, 803 | 3, 0, 1, 1, 804 | 3, 0, 0, 1 805 | )) 806 | 807 | typeIc = StepPattern(_c( 808 | 1, 2, 1, -1, 809 | 1, 1, 0, 1, 810 | 1, 0, 0, 1, 811 | 2, 1, 1, -1, 812 | 2, 0, 0, 1, 813 | 3, 1, 2, -1, 814 | 3, 0, 1, 1, 815 | 3, 0, 0, 0 816 | ), "N") 817 | 818 | typeId = StepPattern(_c( 819 | 1, 2, 1, -1, 820 | 1, 1, 0, 2, 821 | 1, 0, 0, 1, 822 | 2, 1, 1, -1, 823 | 2, 0, 0, 2, 824 | 3, 1, 2, -1, 825 | 3, 0, 1, 2, 826 | 3, 0, 0, 1 827 | ), "N+M") 828 | 829 | ## ---------- 830 | ## smoothed variants of above 831 | 832 | typeIas = StepPattern(_c( 833 | 1, 2, 1, -1, 834 | 1, 1, 0, .5, 835 | 1, 0, 0, .5, 836 | 2, 1, 1, -1, 837 | 2, 0, 0, 1, 838 | 3, 1, 2, -1, 839 | 3, 0, 1, .5, 840 | 3, 0, 0, .5 841 | )) 842 | 843 | typeIbs = StepPattern(_c( 844 | 1, 2, 1, -1, 845 | 1, 1, 0, 1, 846 | 1, 0, 0, 1, 847 | 2, 1, 1, -1, 848 | 2, 0, 0, 1, 849 | 3, 1, 2, -1, 850 | 3, 0, 1, 1, 851 | 3, 0, 0, 1 852 | )) 853 | 854 | typeIcs = StepPattern(_c( 855 | 1, 2, 1, -1, 856 | 1, 1, 0, 1, 857 | 1, 0, 0, 1, 858 | 2, 1, 1, -1, 859 | 2, 0, 0, 1, 860 | 3, 1, 2, -1, 861 | 3, 0, 1, .5, 862 | 3, 0, 0, .5 863 | ), "N") 864 | 865 | typeIds = StepPattern(_c( 866 | 1, 2, 1, -1, 867 | 1, 1, 0, 1.5, 868 | 1, 0, 0, 1.5, 869 | 2, 1, 1, -1, 870 | 2, 0, 0, 2, 871 | 3, 1, 2, -1, 872 | 3, 0, 1, 1.5, 873 | 3, 0, 0, 1.5 874 | ), "N+M") 875 | 876 | ## ---------- 877 | 878 | typeIIa = StepPattern(_c( 879 | 1, 1, 1, -1, 880 | 1, 0, 0, 1, 881 | 2, 1, 2, -1, 882 | 2, 0, 0, 1, 883 | 3, 2, 1, -1, 884 | 3, 0, 0, 1 885 | )) 886 | 887 | typeIIb = StepPattern(_c( 888 | 1, 1, 1, -1, 889 | 1, 0, 0, 1, 890 | 2, 1, 2, -1, 891 | 2, 0, 0, 2, 892 | 3, 2, 1, -1, 893 | 3, 0, 0, 2 894 | )) 895 | 896 | typeIIc = StepPattern(_c( 897 | 1, 1, 1, -1, 898 | 1, 0, 0, 1, 899 | 2, 1, 2, -1, 900 | 2, 0, 0, 1, 901 | 3, 2, 1, -1, 902 | 3, 0, 0, 2 903 | ), "N") 904 | 905 | typeIId = StepPattern(_c( 906 | 1, 1, 1, -1, 907 | 1, 0, 0, 2, 908 | 2, 1, 2, -1, 909 | 2, 0, 0, 3, 910 | 3, 2, 1, -1, 911 | 3, 0, 0, 3 912 | ), "N+M") 913 | 914 | ## ---------- 915 | 916 | ## Rabiner [3] discusses why this is not equivalent to Itakura's 917 | 918 | typeIIIc = StepPattern(_c( 919 | 1, 1, 2, -1, 920 | 1, 0, 0, 1, 921 | 2, 1, 1, -1, 922 | 2, 0, 0, 1, 923 | 3, 2, 1, -1, 924 | 3, 1, 0, 1, 925 | 3, 0, 0, 1, 926 | 4, 2, 2, -1, 927 | 4, 1, 0, 1, 928 | 4, 0, 0, 1 929 | ), "N") 930 | 931 | ## ---------- 932 | 933 | ## numbers follow as production rules in fig 2.16 934 | 935 | typeIVc = StepPattern(_c( 936 | 1, 1, 1, -1, 937 | 1, 0, 0, 1, 938 | 2, 1, 2, -1, 939 | 2, 0, 0, 1, 940 | 3, 1, 3, -1, 941 | 3, 0, 0, 1, 942 | 4, 2, 1, -1, 943 | 4, 1, 0, 1, 944 | 4, 0, 0, 1, 945 | 5, 2, 2, -1, 946 | 5, 1, 0, 1, 947 | 5, 0, 0, 1, 948 | 6, 2, 3, -1, 949 | 6, 1, 0, 1, 950 | 6, 0, 0, 1, 951 | 7, 3, 1, -1, 952 | 7, 2, 0, 1, 953 | 7, 1, 0, 1, 954 | 7, 0, 0, 1, 955 | 8, 3, 2, -1, 956 | 8, 2, 0, 1, 957 | 8, 1, 0, 1, 958 | 8, 0, 0, 1, 959 | 9, 3, 3, -1, 960 | 9, 2, 0, 1, 961 | 9, 1, 0, 1, 962 | 9, 0, 0, 1 963 | ), "N") 964 | 965 | ############################# 966 | ## 967 | ## Mori's asymmetric step-constrained pattern. Normalized in the 968 | ## reference length. 969 | ## 970 | ## Mori, A.; Uchida, S.; Kurazume, R.; Taniguchi, R.; Hasegawa, T. & 971 | ## Sakoe, H. Early Recognition and Prediction of Gestures Proc. 18th 972 | ## International Conference on Pattern Recognition ICPR 2006, 2006, 3, 973 | ## 560-563 974 | ## 975 | 976 | mori2006 = StepPattern(_c( 977 | 1, 2, 1, -1, 978 | 1, 1, 0, 2, 979 | 1, 0, 0, 1, 980 | 2, 1, 1, -1, 981 | 2, 0, 0, 3, 982 | 3, 1, 2, -1, 983 | 3, 0, 1, 3, 984 | 3, 0, 0, 3 985 | ), "M") 986 | 987 | ## Completely unflexible: fixed slope 1. Only makes sense with 988 | ## open.begin and open.end 989 | rigid = StepPattern(_c(1, 1, 1, -1, 990 | 1, 0, 0, 1), "N") 991 | -------------------------------------------------------------------------------- /dtw/warp.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """Warp one timeseries into the other""" 21 | 22 | import numpy 23 | import scipy.interpolate 24 | 25 | 26 | # Ties in x are removed and their y mean is used 27 | def _solveTies(x,y): 28 | n = numpy.bincount(x) 29 | s = numpy.bincount(x,y) 30 | return numpy.arange(len(n)), s/n 31 | 32 | # Should mimic R's stats::approx as closely as possible 33 | def _interp(x, y): 34 | xt, yt = _solveTies(x,y) 35 | return scipy.interpolate.interp1d(xt, yt) 36 | 37 | 38 | def warp(d, index_reference=False): 39 | # IMPORT_RDOCSTRING warp 40 | """Apply a warping to a given timeseries 41 | 42 | Returns the indexing required to apply the optimal warping curve to a 43 | given timeseries (warps either into a query or into a reference). 44 | 45 | **Details** 46 | 47 | The warping is returned as a set of indices, which can be used to 48 | subscript the timeseries to be warped (or rows in a matrix, if one wants 49 | to warp a multivariate time series). In other words, ``warp`` converts 50 | the warping curve, or its inverse, into a function in the explicit form. 51 | 52 | Multiple indices that would be mapped to a single point are averaged, 53 | with a warning. Gaps in the index sequence are filled by linear 54 | interpolation. 55 | 56 | Parameters 57 | ---------- 58 | d : 59 | `dtw` object specifying the warping curve to apply 60 | index_reference : 61 | `True` to warp a reference, `False` to warp a query 62 | 63 | Returns 64 | ------- 65 | 66 | A list of indices to subscript the timeseries. 67 | 68 | Examples 69 | -------- 70 | >>> from dtw import * 71 | >>> import numpy as np 72 | 73 | Default test data 74 | 75 | >>> (query, reference) = dtw_test_data.sin_cos() 76 | 77 | >>> alignment = dtw(query,reference); 78 | 79 | >>> wq = warp(alignment,index_reference=False) 80 | >>> wt = warp(alignment,index_reference=True) 81 | 82 | >>> import matplotlib.pyplot as plt; # doctest: +SKIP 83 | ... plt.plot(reference); 84 | ... plt.plot(query[wq]); 85 | ... plt.gca().set_title("Warping query") 86 | 87 | >>> import matplotlib.pyplot as plt; # doctest: +SKIP 88 | ... plt.plot(query); 89 | ... plt.plot(reference[wt]); 90 | ... plt.gca().set_title("Warping reference") 91 | 92 | Asymmetric step makes it "natural" to warp 93 | the reference, because every query index has 94 | exactly one image (q->t is a function) 95 | 96 | >>> alignment = dtw(query,reference,step_pattern=asymmetric) 97 | >>> wt = warp(alignment,index_reference=True); 98 | 99 | >>> plt.plot(query, "b-") # doctest: +SKIP 100 | ... plt.plot(reference[wt], "ok", facecolors='none') 101 | 102 | """ 103 | # ENDIMPORT 104 | if not index_reference: 105 | iset = d.index1 106 | jset = d.index2 107 | else: 108 | iset = d.index2 109 | jset = d.index1 110 | 111 | jmax = numpy.max(jset) 112 | 113 | # Scipy interp1d is buggy. it does not deal with leading 114 | # duplicated values of x. It returns different values 115 | # depending on the dtypes of arguments. 116 | ifun = _interp(x=jset, y=iset) 117 | ii = ifun(numpy.arange(jmax+1)) 118 | 119 | # Quick fix for bug 120 | if numpy.isnan(ii[0]): 121 | ii[numpy.isnan(ii)] = iset[0] 122 | return ii.astype(int) 123 | -------------------------------------------------------------------------------- /dtw/warpArea.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | """Warping path area computation""" 21 | 22 | import numpy 23 | import scipy.interpolate 24 | 25 | 26 | def warpArea(d): 27 | # IMPORT_RDOCSTRING warpArea 28 | """Compute Warping Path Area 29 | 30 | Compute the area between the warping function and the diagonal 31 | (no-warping) path, in unit steps. 32 | 33 | **Details** 34 | 35 | Above- and below- diagonal unit areas all count *plus* one (they do not 36 | cancel with each other). The “diagonal” goes from one corner to the 37 | other of the possibly rectangular cost matrix, therefore having a slope 38 | of ``M/N``, not 1, as in [slantedBandWindow()]. 39 | 40 | The computation is approximate: points having multiple correspondences 41 | are averaged, and points without a match are interpolated. Therefore, 42 | the area can be fractionary. 43 | 44 | Parameters 45 | ---------- 46 | d : 47 | an object of class `dtw` 48 | 49 | Returns 50 | ------- 51 | 52 | The area, not normalized by path length or else. 53 | 54 | Notes 55 | ----- 56 | 57 | There could be alternative definitions to the area, including 58 | considering the envelope of the path. 59 | 60 | Examples 61 | -------- 62 | >>> from dtw import * 63 | 64 | >>> ds = dtw( [1,2,3,4], [1,2,3,4,5,6,7,8]); 65 | 66 | >>> import matplotlib.pyplot as plt; 67 | ... ds.plot(); plt.plot([0,2.3,4.7,7]) # doctest: +SKIP 68 | 69 | >>> warpArea(ds) # doctest: +SKIP 70 | 71 | The area is not the expected result due different assumptions 72 | used in the scipy.interpolate.interp1d funtion. 73 | 74 | >>> ## Result: 6 75 | >>> ## index 2 is 2 while diag is 3_3 (+1_3) 76 | >>> ## 3 3 5_7 (+2_7) 77 | >>> ## 4 4:8 (avg to 6) 8 (+2 ) 78 | >>> ## -------- 79 | >>> ## 6 80 | 81 | """ 82 | # ENDIMPORT 83 | 84 | # interp1d is buggy. it does not deal with duplicated values of x 85 | # leading. it returns different values depending on the dtypes of 86 | # arguments. 87 | ifun = scipy.interpolate.interp1d(x=d.index1, y=d.index2) 88 | ii = ifun(numpy.arange(d.N)) 89 | 90 | # Kludge 91 | if numpy.isnan(ii[0]): 92 | ii[numpy.isnan(ii)] = d.index2[0] 93 | 94 | dg = numpy.linspace(0, d.M - 1, num=d.N) 95 | 96 | ad = numpy.abs(ii - dg) 97 | return numpy.sum(ad) 98 | -------------------------------------------------------------------------------- /dtw/window.py: -------------------------------------------------------------------------------- 1 | ## 2 | ## Copyright (c) 2006-2019 of Toni Giorgino 3 | ## 4 | ## This file is part of the DTW package. 5 | ## 6 | ## DTW is free software: you can redistribute it and/or modify it 7 | ## under the terms of the GNU General Public License as published by 8 | ## the Free Software Foundation, either version 3 of the License, or 9 | ## (at your option) any later version. 10 | ## 11 | ## DTW is distributed in the hope that it will be useful, but WITHOUT 12 | ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14 | ## License for more details. 15 | ## 16 | ## You should have received a copy of the GNU General Public License 17 | ## along with DTW. If not, see . 18 | ## 19 | 20 | 21 | # IMPORT_RDOCSTRING dtwWindowingFunctions 22 | """Global constraints and windowing functions for DTW 23 | 24 | Various global constraints (windows) which can be applied to the 25 | ``window_type`` argument of [dtw()], including the Sakoe-Chiba band, the 26 | Itakura parallelogram, and custom functions. 27 | 28 | **Details** 29 | 30 | Windowing functions can be passed to the ``window_type`` argument in 31 | [dtw()] to put a global constraint to the warping paths allowed. They 32 | take two integer arguments (plus optional parameters) and must return a 33 | boolean value ``True`` if the coordinates fall within the allowed region 34 | for warping paths, ``False`` otherwise. 35 | 36 | User-defined functions can read variables ``reference_size``, 37 | ``query_size`` and ``window_size``; these are pre-set upon invocation. 38 | Some functions require additional parameters which must be set (e_g. 39 | ``window_size``). User-defined functions are free to implement any 40 | window shape, as long as at least one path is allowed between the 41 | initial and final alignment points, i_e., they are compatible with the 42 | DTW constraints. 43 | 44 | The ``sakoeChibaWindow`` function implements the Sakoe-Chiba band, i_e. 45 | ``window_size`` elements around the ``main`` diagonal. If the window 46 | size is too small, i_e. if ``reference_size``-``query_size`` > 47 | ``window_size``, warping becomes impossible. 48 | 49 | An ``itakuraWindow`` global constraint is still provided with this 50 | package. See example below for a demonstration of the difference between 51 | a local the two. 52 | 53 | The ``slantedBandWindow`` (package-specific) is a band centered around 54 | the (jagged) line segment which joins element ``[1,1]`` to element 55 | ``[query_size,reference_size]``, and will be ``window_size`` columns 56 | wide. In other words, the “diagonal” goes from one corner to the other 57 | of the possibly rectangular cost matrix, therefore having a slope of 58 | ``M/N``, not 1. 59 | 60 | ``dtwWindow_plot`` visualizes a windowing function. By default it plots 61 | a 200 x 220 rectangular region, which can be changed via 62 | ``reference_size`` and ``query_size`` arguments. 63 | 64 | Parameters 65 | ---------- 66 | iw : 67 | index in the query (row) -- automatically set 68 | jw : 69 | index in the reference (column) -- automatically set 70 | query_size : 71 | size of the query time series -- automatically set 72 | reference_size : 73 | size of the reference time series -- automatically set 74 | window_size : 75 | window size, used by some windowing functions -- must be set 76 | fun : 77 | a windowing function 78 | ... : 79 | additional arguments passed to windowing functions 80 | 81 | Returns 82 | ------- 83 | 84 | Windowing functions return ``True`` if the coordinates passed as 85 | arguments fall within the chosen warping window, ``False`` otherwise. 86 | User-defined functions should do the same. 87 | 88 | Notes 89 | ----- 90 | 91 | Although ``dtwWindow_plot`` resembles object-oriented notation, there is 92 | not a such a dtwWindow class currently. 93 | 94 | A widely held misconception is that the “Itakura parallelogram” (as 95 | described in reference 2) is a *global* constraint, i_e. a window. To 96 | the author’s knowledge, it instead arises from the local slope 97 | restrictions imposed to the warping path, such as the one implemented by 98 | the [typeIIIc()] step pattern. 99 | 100 | References 101 | ---------- 102 | 103 | 1. Sakoe, H.; Chiba, S., *Dynamic programming algorithm optimization for 104 | spoken word recognition,* Acoustics, Speech, and Signal Processing, 105 | IEEE Transactions on , vol.26, no.1, pp. 43-49, Feb 1978 106 | `doi:10.1109/TASSP.1978.1163055 `__ 107 | 2. Itakura, F., *Minimum prediction residual principle applied to speech 108 | recognition,* Acoustics, Speech, and Signal Processing, IEEE 109 | Transactions on , vol.23, no.1, pp. 67-72, Feb 1975. 110 | `doi:10.1109/TASSP.1975.1162641 `__ 111 | 112 | Examples 113 | -------- 114 | >>> from dtw import * 115 | >>> import numpy as np 116 | 117 | Default test data 118 | 119 | >>> (query, reference) = dtw_test_data.sin_cos() 120 | 121 | Asymmetric step with Sakoe-Chiba band 122 | 123 | >>> asyband = dtw(query,reference, 124 | ... keep_internals=True, step_pattern=asymmetric, 125 | ... window_type=sakoeChibaWindow, 126 | ... window_args={'window_size': 30} ); 127 | 128 | >>> dtwPlot(asyband,type="density") # doctest: +SKIP 129 | 130 | Display some windowing functions 131 | 132 | >>> #TODO dtwWindow_plot(itakuraWindow, main="So-called Itakura parallelogram window") 133 | >>> #TODO dtwWindow_plot(slantedBandWindow, window_size=2, 134 | >>> #TODO reference=13, query=17, main="The slantedBandWindow at window_size=2") 135 | 136 | """ 137 | # ENDIMPORT 138 | 139 | 140 | 141 | # The functions must be vectorized! The first 2 args are matrices of row and column indices. 142 | 143 | def noWindow(iw, jw, query_size, reference_size): 144 | return (iw | True) 145 | 146 | 147 | def sakoeChibaWindow(iw, jw, query_size, reference_size, window_size): 148 | ok = abs(jw - iw) <= window_size 149 | return ok 150 | 151 | 152 | def itakuraWindow(iw, jw, query_size, reference_size): 153 | n = query_size 154 | m = reference_size 155 | ok = (jw < 2 * iw) & \ 156 | (iw <= 2 * jw) & \ 157 | (iw >= n - 1 - 2 * (m - jw)) & \ 158 | (jw > m - 1 - 2 * (n - iw)) 159 | return ok 160 | 161 | 162 | def slantedBandWindow(iw, jw, query_size, reference_size, window_size): 163 | diagj = (iw * reference_size / query_size) 164 | return abs(jw - diagj) <= window_size 165 | -------------------------------------------------------------------------------- /maintainer/README.maintainer: -------------------------------------------------------------------------------- 1 | Notes to self 2 | ============= 3 | 4 | 5 | 6 | 7 | 8 | Deploying 9 | --------- 10 | 11 | Make sure all your changes are committed (including an entry in HISTORY.rst). 12 | Then run:: 13 | 14 | $ bumpversion patch # possible: major / minor / patch 15 | $ git push --follow-tags 16 | $ v=$(git tag --points-at HEAD) && gh release create $v --title $v 17 | 18 | Actions will then deploy to PyPI if tests pass. 19 | 20 | 21 | Misc stuff 22 | ---------- 23 | 24 | Uses cibuildwheel on GH actions https://cibuildwheel.readthedocs.io/en/stable/setup/#github-actions 25 | 26 | 27 | Setup.cfg 28 | --------- 29 | 30 | Necessary because bumpversion 31 | 32 | 33 | 34 | Poetry 35 | ------ 36 | 37 | Nice, but cython support is not stable. 38 | 39 | 40 | 41 | The roxypick script 42 | ------------ 43 | 44 | - extract roxygen2 strings 45 | - writes examples in examples/*.R 46 | - writes also in examples/*.py.draft after minor conversions 47 | - puts sections (converted in rst) in-place in the python files, at RIMPORT 48 | - adds an example section from examples/*.py.txt , if found 49 | 50 | sep 2020 51 | 52 | The roxygen2 api changed. Also, some examples needed manual tweaking anyway. 53 | 54 | -------------------------------------------------------------------------------- /maintainer/examples/ex_aami.py.txt: -------------------------------------------------------------------------------- 1 | >>> from dtw import *; import numpy as np 2 | >>> (aami3a, aami3b) = dtw_test_data.aami() 3 | 4 | Timestamps (ms) are in the first row, values (mV) in the second. 5 | 6 | >>> with np.printoptions(precision=3): print(aami3a[0,:]) # doctest: +NORMALIZE_WHITESPACE 7 | [0.000e+00 1.389e+00 2.778e+00 ... 5.983e+04 5.983e+04 5.983e+04] 8 | 9 | >>> with np.printoptions(precision=3): print(aami3a[1,:]) # doctest: +NORMALIZE_WHITESPACE 10 | [0.185 0.185 0.169 ... 0.208 0.208 0.208] 11 | -------------------------------------------------------------------------------- /maintainer/examples/ex_countPaths.py.txt: -------------------------------------------------------------------------------- 1 | 2 | >>> from dtw import * 3 | >>> ds = dtw( numpy.arange(3,10), numpy.arange(1,9), 4 | ... keep_internals=True, step_pattern=asymmetric); 5 | >>> countPaths(ds) 6 | 126.0 7 | 8 | -------------------------------------------------------------------------------- /maintainer/examples/ex_dtw.py.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | >>> import numpy as np 4 | >>> from dtw import * 5 | 6 | A noisy sine wave as query 7 | 8 | >>> idx = np.linspace(0,6.28,num=100) 9 | >>> query = np.sin(idx) + np.random.uniform(size=100)/10.0 10 | 11 | A cosine is for reference; sin and cos are offset by 25 samples 12 | 13 | >>> reference = np.cos(idx) 14 | 15 | Find the best match 16 | 17 | >>> alignment = dtw(query,reference) 18 | 19 | Display the mapping, AKA warping function - may be multiple-valued 20 | Equivalent to: plot(alignment,type="alignment") 21 | 22 | >>> import matplotlib.pyplot as plt; 23 | ... plt.plot(alignment.index1, alignment.index2) # doctest: +SKIP 24 | 25 | 26 | Partial alignments are allowed. 27 | 28 | >>> alignmentOBE = dtw(query[44:88], reference, 29 | ... keep_internals=True, 30 | ... step_pattern=asymmetric, 31 | ... open_end=True,open_begin=True) 32 | 33 | >>> alignmentOBE.plot(type="twoway",offset=1) # doctest: +SKIP 34 | 35 | 36 | Subsetting allows warping and unwarping of 37 | timeseries according to the warping curve. 38 | See first example below. 39 | 40 | Most useful: plot the warped query along with reference 41 | >>> plt.plot(reference); 42 | ... plt.plot(alignment.index2,query[alignment.index1]) # doctest: +SKIP 43 | 44 | Plot the (unwarped) query and the inverse-warped reference 45 | >>> plt.plot(query) # doctest: +SKIP 46 | ... plt.plot(alignment.index1,reference[alignment.index2]) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | A hand-checkable example 56 | 57 | >>> ldist = np.ones((6,6)) # Matrix of ones 58 | >>> ldist[1,:] = 0; ldist[:,4] = 0; # Mark a clear path of zeroes 59 | >>> ldist[1,4] = .01; # Forcely cut the corner 60 | 61 | >>> ds = dtw(ldist); # DTW with user-supplied local 62 | 63 | >>> da = dtw(ldist,step_pattern=asymmetric) # Also compute the asymmetric 64 | 65 | Symmetric: alignment follows the low-distance marked path 66 | >>> plt.plot(ds.index1,ds.index2) # doctest: +SKIP 67 | 68 | Asymmetric: visiting 1 is required twice 69 | >>> plt.plot(da.index1,da.index2,'ro') # doctest: +SKIP 70 | 71 | >>> ds.distance 72 | 2.0 73 | >>> da.distance 74 | 2.0 75 | 76 | -------------------------------------------------------------------------------- /maintainer/examples/ex_dtwPlotDensity.py.txt: -------------------------------------------------------------------------------- 1 | >>> from dtw import * 2 | 3 | A study of the "Itakura" parallelogram 4 | 5 | A widely held misconception is that the "Itakura parallelogram" (as 6 | described in the original article) is a global constraint. Instead, 7 | it arises from local slope restrictions. Anyway, an "itakuraWindow", 8 | is provided in this package. A comparison between the two follows. 9 | 10 | The local constraint: three sides of the parallelogram are seen 11 | 12 | >>> (query, reference) = dtw_test_data.sin_cos() 13 | >>> ita = dtw(query, reference, keep_internals=True, step_pattern=typeIIIc) 14 | 15 | >>> dtwPlotDensity(ita) # doctest: +SKIP 16 | 17 | 18 | Symmetric step with global parallelogram-shaped constraint. Note how 19 | long (>2 steps) horizontal stretches are allowed within the window. 20 | 21 | >>> ita = dtw(query, reference, keep_internals=True, window_type=itakuraWindow) 22 | 23 | >>> dtwPlotDensity(ita) # doctest: +SKIP 24 | 25 | -------------------------------------------------------------------------------- /maintainer/examples/ex_dtwWindowingFunctions.py.txt: -------------------------------------------------------------------------------- 1 | >>> from dtw import * 2 | >>> import numpy as np 3 | 4 | Default test data 5 | 6 | >>> (query, reference) = dtw_test_data.sin_cos() 7 | 8 | 9 | Asymmetric step with Sakoe-Chiba band 10 | 11 | >>> asyband = dtw(query,reference, 12 | ... keep_internals=True, step_pattern=asymmetric, 13 | ... window_type=sakoeChibaWindow, 14 | ... window_args={'window_size': 30} ); 15 | 16 | 17 | >>> dtwPlot(asyband,type="density") # doctest: +SKIP 18 | 19 | 20 | 21 | 22 | Display some windowing functions 23 | 24 | >>> #TODO dtwWindow_plot(itakuraWindow, main="So-called Itakura parallelogram window") 25 | >>> #TODO dtwWindow_plot(slantedBandWindow, window_size=2, 26 | >>> #TODO reference=13, query=17, main="The slantedBandWindow at window_size=2") 27 | -------------------------------------------------------------------------------- /maintainer/examples/ex_mvmStepPattern.py.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | >>> import numpy as np 4 | >>> from dtw import * 5 | 6 | The hand-checkable example given in Fig. 5, ref. [1] above 7 | 8 | >>> diffmx = np.array( 9 | ... [[ 0, 1, 8, 2, 2, 4, 8 ], 10 | ... [ 1, 0, 7, 1, 1, 3, 7 ], 11 | ... [ -7, -6, 1, -5, -5, -3, 1 ], 12 | ... [ -5, -4, 3, -3, -3, -1, 3 ], 13 | ... [ -7, -6, 1, -5, -5, -3, 1 ]], dtype=np.double ) 14 | 15 | 16 | Cost matrix 17 | 18 | >>> costmx = diffmx**2; 19 | 20 | 21 | Compute the alignment 22 | 23 | >>> al = dtw(costmx,step_pattern=mvmStepPattern(10)) 24 | 25 | 26 | Elements 4,5 are skipped 27 | 28 | >>> al.index2+1 29 | array([1, 2, 3, 6, 7]) 30 | 31 | >>> al.plot() # doctest: +SKIP 32 | 33 | -------------------------------------------------------------------------------- /maintainer/examples/ex_stepPattern.py.txt: -------------------------------------------------------------------------------- 1 | >>> from dtw import * 2 | >>> import numpy as np 3 | 4 | The usual (normalizable) symmetric step pattern 5 | Step pattern recursion, defined as: 6 | g[i,j] = min( 7 | g[i,j-1] + d[i,j] , 8 | g[i-1,j-1] + 2 * d[i,j] , 9 | g[i-1,j] + d[i,j] , 10 | ) 11 | 12 | 13 | >>> print(symmetric2) #doctest: +NORMALIZE_WHITESPACE 14 | Step pattern recursion: 15 | g[i,j] = min( 16 | g[i-1,j-1] + 2 * d[i ,j ] , 17 | g[i ,j-1] + d[i ,j ] , 18 | g[i-1,j ] + d[i ,j ] , 19 | ) 20 | 21 | Normalization hint: N+M 22 | 23 | 24 | 25 | The well-known plotting style for step patterns 26 | 27 | >>> import matplotlib.pyplot as plt; # doctest: +SKIP 28 | ... symmetricP2.plot().set_title("Sakoe's Symmetric P=2 recursion") 29 | 30 | 31 | 32 | 33 | Same example seen in ?dtw , now with asymmetric step pattern 34 | 35 | >>> (query, reference) = dtw_test_data.sin_cos() 36 | 37 | Do the computation 38 | 39 | >>> asy = dtw(query, reference, keep_internals=True, 40 | ... step_pattern=asymmetric); 41 | 42 | 43 | >>> dtwPlot(asy,type="density" # doctest: +SKIP 44 | ... ).set_title("Sine and cosine, asymmetric step") 45 | 46 | 47 | 48 | Hand-checkable example given in [Myers1980] p 61 - see JSS paper 49 | 50 | >>> tm = numpy.reshape( [1, 3, 4, 4, 5, 2, 2, 3, 3, 4, 3, 1, 1, 1, 3, 4, 2, 51 | ... 3, 3, 2, 5, 3, 4, 4, 1], (5,5) ) 52 | 53 | -------------------------------------------------------------------------------- /maintainer/examples/ex_warp.py.txt: -------------------------------------------------------------------------------- 1 | >>> from dtw import * 2 | >>> import numpy as np 3 | 4 | Default test data 5 | 6 | >>> (query, reference) = dtw_test_data.sin_cos() 7 | 8 | >>> alignment = dtw(query,reference); 9 | 10 | 11 | >>> wq = warp(alignment,index_reference=False) 12 | >>> wt = warp(alignment,index_reference=True) 13 | 14 | 15 | >>> import matplotlib.pyplot as plt; # doctest: +SKIP 16 | ... plt.plot(reference); 17 | ... plt.plot(query[wq]); 18 | ... plt.gca().set_title("Warping query") 19 | 20 | >>> import matplotlib.pyplot as plt; # doctest: +SKIP 21 | ... plt.plot(query); 22 | ... plt.plot(reference[wt]); 23 | ... plt.gca().set_title("Warping reference") 24 | 25 | 26 | 27 | 28 | 29 | 30 | Asymmetric step makes it "natural" to warp 31 | the reference, because every query index has 32 | exactly one image (q->t is a function) 33 | 34 | >>> alignment = dtw(query,reference,step_pattern=asymmetric) 35 | >>> wt = warp(alignment,index_reference=True); 36 | 37 | 38 | >>> plt.plot(query, "b-") # doctest: +SKIP 39 | ... plt.plot(reference[wt], "ok", facecolors='none') 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /maintainer/examples/ex_warpArea.py.txt: -------------------------------------------------------------------------------- 1 | >>> from dtw import * 2 | 3 | >>> ds = dtw( [1,2,3,4], [1,2,3,4,5,6,7,8]); 4 | 5 | 6 | >>> import matplotlib.pyplot as plt; 7 | ... ds.plot(); plt.plot([0,2.3,4.7,7]) # doctest: +SKIP 8 | 9 | 10 | >>> warpArea(ds) 11 | 8.0 12 | 13 | The area is not the expected result due different assumptions 14 | used in the scipy.interpolate.interp1d funtion. 15 | 16 | >>> ## Result: 6 17 | >>> ## index 2 is 2 while diag is 3_3 (+1_3) 18 | >>> ## 3 3 5_7 (+2_7) 19 | >>> ## 4 4:8 (avg to 6) 8 (+2 ) 20 | >>> ## -------- 21 | >>> ## 6 22 | 23 | -------------------------------------------------------------------------------- /maintainer/roxypick.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | 5 | from rpy2.robjects.packages import importr 6 | 7 | import glob 8 | import re 9 | import os 10 | import pypandoc 11 | 12 | 13 | 14 | # ================================================== 15 | # Misc functions 16 | 17 | def indent_as(l): 18 | """Return the indentation before hash""" 19 | c = l.find('#') 20 | return l[:c] 21 | 22 | def dot_underscore(s): 23 | """R to Py""" 24 | rex = r"([a-zA-Z])\.([a-zA-Z])" 25 | s = re.sub(rex,r"\1_\2",s) 26 | s = s.replace("TRUE","True") 27 | s = s.replace("FALSE","False") 28 | return s 29 | 30 | def sanitize_rd_commands(s): 31 | """E.g. \doi""" 32 | import re 33 | s = re.sub('\\\\url{(.+?)}','<\\1>',s) 34 | s = re.sub('\\\\doi{(.+?)}','[doi:\\1](https://doi.org/\\1)',s) 35 | return s 36 | 37 | 38 | 39 | def unarrow(s): 40 | s=s.replace("<-"," = ") 41 | return s 42 | 43 | 44 | def getParameters(k): 45 | """Print out parameters list""" 46 | o=[] 47 | 48 | if "param" not in k: 49 | return "" 50 | 51 | for p in k['param']: 52 | pn = dot_underscore(p.rx2('name')[0]) 53 | pd = dot_underscore(p.rx2('description')[0]) 54 | pd = pd.replace("\n"," ") 55 | o.append(pn + " : ") 56 | o.append( " " + pd ) 57 | if len(o) > 0: 58 | out = "Parameters\n" 59 | out += "----------\n" 60 | out += ("\n".join(o)) 61 | return out 62 | else: 63 | return "" 64 | 65 | 66 | 67 | def p(k,w,h=None): 68 | """Get section w of key k""" 69 | try: 70 | txt = "\n".join(k[w]) 71 | txt = dot_underscore(txt) 72 | txt = sanitize_rd_commands(txt) 73 | txt_m = pypandoc.convert_text(txt,'rst',format="md") 74 | out = "" 75 | if h is not None: 76 | out = h + "\n" 77 | out += ("-" * len(h)) + "\n\n" 78 | out += txt_m + "\n\n" 79 | return out 80 | except: 81 | return "" 82 | 83 | 84 | 85 | def getdoc(n): 86 | k=roxy[n] 87 | 88 | o=f"""\"\"\"{p(k,'title')} 89 | 90 | {p(k,'description')} 91 | 92 | **Details** 93 | 94 | {p(k,'details')} 95 | 96 | {getParameters(k)} 97 | 98 | {p(k,'return','Returns')} 99 | 100 | {p(k,'note','Notes')} 101 | 102 | {p(k,'references','References')} 103 | 104 | {getex(n)} 105 | \"\"\" 106 | """ 107 | o=sanitize_whitespace(o) 108 | return o 109 | 110 | def sanitize_whitespace(o): 111 | """Remove duplicated empty lines""" 112 | return re.sub(r'\n\s*\n', '\n\n', o) 113 | 114 | 115 | def convex(txt): 116 | o = [] 117 | for l in txt.split("\n"): 118 | if l.startswith("##"): 119 | l = l.replace("#","") 120 | l = l.lstrip() 121 | elif re.match(r'^ *$',l): 122 | l = "\n" 123 | else: 124 | l = l.lstrip() 125 | l = dot_underscore(l) 126 | l = unarrow(l) 127 | l = ">>> "+l 128 | o.append(l) 129 | return "\n".join(o) 130 | 131 | 132 | def getex(n): 133 | o="" 134 | try: 135 | with open(f"maintainer/examples/ex_{n}.py.txt") as f: 136 | o = "Examples\n" 137 | o += "--------\n" 138 | o += f.read() 139 | o += "\n\n" 140 | except: 141 | print(f"No examples for {n}") 142 | return o 143 | 144 | 145 | 146 | # ======================================== 147 | # ======================================== 148 | 149 | 150 | 151 | 152 | roxy = {} 153 | pyex = {} 154 | 155 | # ======================================== 156 | # Parse the roxygen headers 157 | 158 | # For roxygen 7.1.1 this became elts[0].rx2("tags")[2].rx2("val") 159 | 160 | rlist = glob.glob("../dtw/R/*.R") 161 | 162 | r2=importr("roxygen2") 163 | 164 | for rfile in rlist: 165 | print(f"Parsing {rfile}...") 166 | 167 | elts = r2.parse_file(rfile) 168 | for i,k in enumerate(elts): 169 | tagdict = {} 170 | for tag_r in k.rx2("tags"): 171 | tag = tag_r.rx2("tag")[0] 172 | val = tag_r.rx2("val") 173 | if tag not in tagdict: 174 | tagdict[tag] = [] 175 | if tag == "param": 176 | tagdict[tag].append(val) 177 | else: 178 | tagdict[tag].append(val[0]) 179 | try: 180 | ex = k.rx2('object').rx2('alias')[0] 181 | wh = "alias" 182 | except: 183 | try: 184 | ex = tagdict['name'][0] 185 | wh = "@name" 186 | except: 187 | print(f" {i} NOT FOUND") 188 | continue 189 | 190 | print(f" {i} found {wh}... "+ex) 191 | roxy[ex] = tagdict 192 | 193 | print("\n\n") 194 | 195 | 196 | 197 | # ================================================== 198 | # Write examples 199 | for k in roxy: 200 | try: 201 | ex = roxy[k]["examples"][0] 202 | except: 203 | continue 204 | with open(f"maintainer/examples/ex_{k}.R", "w") as f: 205 | print(f"Writing example: {k}") 206 | f.write(ex) 207 | with open(f"maintainer/examples/ex_{k}.py.draft", "w") as f: 208 | print(f"Writing draft: {k}") 209 | f.write(convex(ex)) 210 | 211 | 212 | 213 | print("\n\n") 214 | 215 | 216 | # ================================================== 217 | # Process all the python files 218 | 219 | plist = glob.glob("dtw/*.py") 220 | 221 | for pfile in plist: 222 | print(f"Modifying {pfile}...") 223 | 224 | pfile_back = pfile+"~" 225 | os.rename(pfile, pfile_back ) 226 | fin=open(pfile_back,"r") 227 | fout=open(pfile, "w") 228 | 229 | for l in fin: 230 | if "IMPORT_RDOCSTRING" in l: 231 | fout.write(l) # Copy the tag 232 | 233 | fout.write(indent_as(l)) # Copy indentation 234 | 235 | n = l.split()[2] # Extract name 236 | print(f" Inserting {n}") 237 | 238 | ds = getdoc(n) 239 | fout.write(ds) 240 | 241 | while True: # Skip until the closing tag 242 | l = fin.readline() 243 | if "ENDIMPORT" in l: 244 | fout.write(l) 245 | break 246 | else: 247 | fout.write(l) 248 | 249 | fin.close() 250 | fout.close() 251 | 252 | 253 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ 2 | 3 | 4 | [build-system] 5 | # Minimum requirements for the build system to execute. 6 | # PEP 508 specifications. 7 | requires = ["setuptools", 8 | "wheel", 9 | "Cython", 10 | 'numpy>=2.0.0rc1; python_version>="3.9"', 11 | 'numpy; python_version<"3.9"', 12 | ] 13 | build-backend = "setuptools.build_meta" 14 | 15 | 16 | # https://numpy.org/devdocs/dev/depending_on_numpy.html#for-downstream-package-authors 17 | 18 | [project] 19 | name = "dtw-python" 20 | dynamic = ["version"] 21 | dependencies = [ 22 | 'numpy>=1.23.5; python_version>="3.9"', 23 | 'numpy; python_version<"3.9"', 24 | "scipy" 25 | ] 26 | requires-python = ">= 3.6" 27 | authors = [ 28 | {name = "Toni Giorgino", email = "toni.giorgino@gmail.com"}, 29 | ] 30 | description = "A comprehensive implementation of dynamic time warping (DTW) algorithms." 31 | readme = "README.rst" 32 | license = {file = "COPYING"} 33 | keywords = ["timeseries", "alignment"] 34 | classifiers = [ 35 | 'Development Status :: 4 - Beta', 36 | 'Intended Audience :: Science/Research', 37 | 'Topic :: Scientific/Engineering', 38 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 39 | 'Natural Language :: English', 40 | 'Programming Language :: Python :: 3.6', 41 | 'Programming Language :: Python :: 3.7', 42 | 'Programming Language :: Python :: 3.8', 43 | 'Programming Language :: Python :: 3.9', 44 | 'Programming Language :: Python :: 3.10', 45 | 'Programming Language :: Python :: 3.11', 46 | 'Programming Language :: Python :: 3.12', 47 | ] 48 | 49 | 50 | [project.urls] 51 | Homepage = "https://dynamictimewarping.github.io/" 52 | Repository = "https://github.com/DynamicTimeWarping/dtw-python" 53 | Documentation = "https://dynamictimewarping.github.io/python/" 54 | 55 | 56 | [project.scripts] 57 | dtw = "dtw.__main__:main" 58 | 59 | [project.optional-dependencies] 60 | test = ["numpy"] 61 | 62 | 63 | 64 | 65 | [tool.setuptools.packages.find] 66 | # https://setuptools.pypa.io/en/latest/userguide/datafiles.html 67 | namespaces = true 68 | where = ["."] 69 | 70 | [tool.setuptools.package-data] 71 | "dtw.data" = ["*.csv"] 72 | 73 | [tool.distutils.bdist_wheel] 74 | universal = true 75 | 76 | [tool.pytest.ini_options] 77 | # https://docs.pytest.org/en/7.3.x/reference/customize.html 78 | addopts = "--import-mode=importlib" 79 | 80 | [tool.ruff] 81 | # https://docs.astral.sh/ruff/configuration/ 82 | # Exclude a variety of commonly ignored directories. 83 | exclude = [ 84 | ".bzr", 85 | ".direnv", 86 | ".eggs", 87 | ".git", 88 | ".git-rewrite", 89 | ".hg", 90 | ".ipynb_checkpoints", 91 | ".mypy_cache", 92 | ".nox", 93 | ".pants.d", 94 | ".pyenv", 95 | ".pytest_cache", 96 | ".pytype", 97 | ".ruff_cache", 98 | ".svn", 99 | ".tox", 100 | ".venv", 101 | ".vscode", 102 | "__pypackages__", 103 | "_build", 104 | "buck-out", 105 | "build", 106 | "dist", 107 | "node_modules", 108 | "site-packages", 109 | "venv", 110 | "docs", 111 | ] 112 | 113 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.5.3 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version="{current_version}" 8 | replace = version="{new_version}" 9 | 10 | [bumpversion:file:dtw/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup 7 | from setuptools.extension import Extension 8 | from Cython.Build import cythonize 9 | import numpy 10 | 11 | 12 | setup( 13 | include_package_data=True, 14 | name="dtw-python", 15 | # packages=find_packages(include=['dtw']), 16 | packages=["dtw"], 17 | include_dirs=numpy.get_include(), 18 | ext_modules=cythonize( 19 | [Extension("dtw._dtw_utils", sources=["dtw/_dtw_utils.pyx", "dtw/dtw_core.c"])], 20 | force=True, 21 | ), 22 | url="https://DynamicTimeWarping.github.io", 23 | version="1.5.3", 24 | zip_safe=False, 25 | ) 26 | -------------------------------------------------------------------------------- /tests/query.csv: -------------------------------------------------------------------------------- 1 | 2.097144222158323773e-02 2 | 1.618177646975575423e-01 3 | 2.134682506650182887e-01 4 | 2.552157161579854394e-01 5 | 3.479645002395120867e-01 6 | 4.044272391980078263e-01 7 | 4.456368031355366632e-01 8 | 4.544769298265479707e-01 9 | 5.595535873718534559e-01 10 | 5.558477206598475551e-01 11 | 6.672438501391244614e-01 12 | 6.581049118534623155e-01 13 | 7.260424153359905652e-01 14 | 8.003686198393912266e-01 15 | 8.321875764593452685e-01 16 | 8.814383265333336492e-01 17 | 9.062301536250213507e-01 18 | 9.090016667700570041e-01 19 | 9.252990653956520983e-01 20 | 9.668108090910074726e-01 21 | 9.756141355467669030e-01 22 | 1.041763916579883986e+00 23 | 1.040022320818534851e+00 24 | 1.061677785265285445e+00 25 | 1.064975984946100684e+00 26 | 1.081210738311875197e+00 27 | 1.009521852539464692e+00 28 | 1.001365357794164801e+00 29 | 1.005320989002763898e+00 30 | 9.905046267738328858e-01 31 | 9.630156581920935599e-01 32 | 9.661168876680977791e-01 33 | 9.376915860541215064e-01 34 | 8.881207055705321318e-01 35 | 8.956598614118379542e-01 36 | 8.568221901575880928e-01 37 | 7.838804385511523032e-01 38 | 7.195209340085552130e-01 39 | 6.999051534123893470e-01 40 | 6.989791525853141785e-01 41 | 6.646806941472818142e-01 42 | 5.774132301845731341e-01 43 | 5.095267914513372798e-01 44 | 4.268466193238092399e-01 45 | 4.379619099630737145e-01 46 | 3.693673686365206432e-01 47 | 3.103885954685144943e-01 48 | 2.528733372110653077e-01 49 | 1.758087838213391596e-01 50 | 8.375774315946553361e-02 51 | 1.778072941547956254e-02 52 | -8.584166773341135592e-02 53 | -1.020275692047293847e-01 54 | -2.138966830539481556e-01 55 | -2.029624149772950026e-01 56 | -3.086353019808671383e-01 57 | -3.893242537129043557e-01 58 | -4.120384897760125398e-01 59 | -5.032735259891655266e-01 60 | -5.046460607469701598e-01 61 | -5.302888850410909516e-01 62 | -6.079358153638397821e-01 63 | -6.874454120108210997e-01 64 | -7.116483956237394937e-01 65 | -7.902062356637106211e-01 66 | -7.743272099088435168e-01 67 | -7.804482063544799786e-01 68 | -8.180339819327029360e-01 69 | -8.309681258687227068e-01 70 | -9.316151844013839112e-01 71 | -9.191006514776214331e-01 72 | -9.230954416883642155e-01 73 | -9.385573951304547746e-01 74 | -9.920850525333329006e-01 75 | -9.596211171927603312e-01 76 | -9.879522869108052774e-01 77 | -9.734661190672743158e-01 78 | -9.343059733660080690e-01 79 | -8.794155224805160209e-01 80 | -8.820825427508721672e-01 81 | -8.991943870789750326e-01 82 | -8.310677887798609831e-01 83 | -8.246260830523535201e-01 84 | -8.418564566093414969e-01 85 | -7.468726506196343706e-01 86 | -7.025699043806725719e-01 87 | -6.937598716037787216e-01 88 | -6.504707289802792447e-01 89 | -6.367107688379050057e-01 90 | -5.579790985841794893e-01 91 | -5.133183624198719430e-01 92 | -4.659813501624126553e-01 93 | -4.074590418957685367e-01 94 | -2.784941208778793920e-01 95 | -3.033796085072174575e-01 96 | -2.397807807019513848e-01 97 | -1.176991566774738956e-01 98 | -9.866840642754810820e-02 99 | 9.736581636363220160e-03 100 | 4.604293293035154055e-02 101 | -------------------------------------------------------------------------------- /tests/reference.csv: -------------------------------------------------------------------------------- 1 | 1.000000000000000000e+00 2 | 9.979887166085150696e-01 3 | 9.919629569558218174e-01 4 | 9.819469600625414518e-01 5 | 9.679810159450751295e-01 6 | 9.501213035463226264e-01 7 | 9.284396647521325763e-01 8 | 9.030233154025187892e-01 9 | 8.739744944601194332e-01 10 | 8.414100527471418678e-01 11 | 8.054609829051265768e-01 12 | 7.662718924682988542e-01 13 | 7.240004221701045184e-01 14 | 6.788166118228324830e-01 15 | 6.309022163211138734e-01 16 | 5.804499745207195582e-01 17 | 5.276628339336426610e-01 18 | 4.727531343581763967e-01 19 | 4.159417537278960464e-01 20 | 3.574572196154193748e-01 21 | 2.975347899649851136e-01 22 | 2.364155067516597875e-01 23 | 1.743452263738961838e-01 24 | 1.115736306797515814e-01 25 | 4.835322260497924157e-02 26 | -1.506168953689365243e-02 27 | -7.841601502673981039e-02 28 | -1.414549068592865499e-01 29 | -2.039247868822129939e-01 30 | -2.655743658312028077e-01 31 | -3.261556541577919632e-01 32 | -3.854249595838878939e-01 33 | -4.431438673702341458e-01 34 | -4.990801993556202021e-01 35 | -5.530089479090399829e-01 36 | -6.047131810379160477e-01 37 | -6.539849150115245591e-01 38 | -7.006259509894445792e-01 39 | -7.444486722896276332e-01 40 | -7.852767990890321403e-01 41 | -8.229460975209843543e-01 42 | -8.573050403168737121e-01 43 | -8.882154163347117937e-01 44 | -9.155528865226800761e-01 45 | -9.392074840812697500e-01 46 | -9.590840568120775345e-01 47 | -9.751026498738768478e-01 48 | -9.871988274063074886e-01 49 | -9.953239317274266140e-01 50 | -9.994452790624840377e-01 51 | -9.995462910165884285e-01 52 | -9.956265612624087824e-01 53 | -9.877018571746521980e-01 54 | -9.758040564047473220e-01 55 | -9.599810186508611620e-01 56 | -9.402963931390683205e-01 57 | -9.168293625900880706e-01 58 | -8.896743247015010958e-01 59 | -8.589405124267088798e-01 60 | -8.247515545780815804e-01 61 | -7.872449785218056384e-01 62 | -7.465716569648682688e-01 63 | -7.028952010595167499e-01 64 | -6.563913022664746855e-01 65 | -6.072470256243046594e-01 66 | -5.556600572678014327e-01 67 | -5.018379092223087534e-01 68 | -4.459970846727440708e-01 69 | -3.883622070650734059e-01 70 | -3.291651165435018900e-01 71 | -2.686439373580091772e-01 72 | -2.070421199936547108e-01 73 | -1.446074618747380802e-01 74 | -8.159111058311369069e-02 75 | -1.824655360027289952e-02 76 | 4.517140136298402470e-02 77 | 1.084076513475788900e-01 78 | 1.712078243148423273e-01 79 | 2.333193023750322892e-01 80 | 2.944922379596627837e-01 81 | 3.544805588500350435e-01 82 | 4.130429580191676830e-01 83 | 4.699438643054327791e-01 84 | 5.249543900132830343e-01 85 | 5.778532516292909094e-01 86 | 6.284276599498636040e-01 87 | 6.764741760400230230e-01 88 | 7.217995295801062916e-01 89 | 7.642213963085372486e-01 90 | 8.035691314333426316e-01 91 | 8.396844560622247489e-01 92 | 8.724220938899741595e-01 93 | 9.016503555821127058e-01 94 | 9.272516685040339546e-01 95 | 9.491230496647770076e-01 96 | 9.671765199729874807e-01 97 | 9.813394581386861715e-01 98 | 9.915548927972585025e-01 99 | 9.977817316805730474e-01 100 | 9.999949269133752150e-01 101 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Tests for `dtw` package.""" 5 | 6 | import os.path 7 | 8 | import unittest 9 | 10 | from dtw.__main__ import * 11 | 12 | here = os.path.dirname(os.path.abspath(__file__)) 13 | 14 | class TestCLI(unittest.TestCase): 15 | """Tests for `dtw` package.""" 16 | 17 | def test_command_line_interface(self): 18 | """Test the CLI.""" 19 | out = main2(os.path.join(here,"query.csv"), 20 | os.path.join(here,"reference.csv"), 21 | "symmetric2") 22 | assert '0.1292' in out 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/test_countPaths.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | import numpy as np 5 | from numpy.testing import * 6 | 7 | from dtw import * 8 | from dtw.countPaths import * 9 | 10 | 11 | class Test_countPaths(unittest.TestCase): 12 | 13 | # From dtw()'s example 14 | def test_example_ds(self): 15 | ldist = np.full( (6,6), 1.0) 16 | ldist[1,:] = 0 17 | ldist[:,4] = 0 18 | ldist[1,4] = .01 19 | ds = dtw(ldist, keep_internals=True) 20 | pds = countPaths(ds) 21 | assert_equal(pds, 1683) 22 | 23 | def test_example_da(self): 24 | ldist = np.full( (6,6), 1.0) 25 | ldist[1,:] = 0 26 | ldist[:,4] = 0 27 | ldist[1,4] = .01 28 | 29 | da = dtw(ldist, step_pattern=asymmetric, keep_internals=True) 30 | pda = countPaths(da) 31 | assert_equal(pda, 51) 32 | 33 | -------------------------------------------------------------------------------- /tests/test_cran.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import (assert_approx_equal, assert_equal, 5 | assert_array_equal) 6 | from dtw import * 7 | 8 | 9 | def i(l): 10 | return np.array([int(x) for x in l.split()], dtype=int)-1 11 | 12 | 13 | ldist = np.full((6, 6), 1.0) 14 | ldist[1, :] = 0 15 | ldist[:, 4] = 0 16 | ldist[1, 4] = .01 17 | 18 | 19 | """Same tests as CRAN checks""" 20 | 21 | 22 | class TestCRAN(unittest.TestCase): 23 | def test_ldist_symmetric2(self): 24 | ds = dtw(ldist, keep_internals=True) 25 | assert_equal(ds.distance, 2) 26 | assert_array_equal(ds.index1, i("1 2 2 2 3 4 5 6 6")) 27 | assert_array_equal(ds.index2, i("1 2 3 4 5 5 5 5 6")) 28 | assert_array_equal(ds.costMatrix, np.array( 29 | [[1, 2, 3, 4, 4.00, 5.00], 30 | [1, 1, 1, 1, 1.01, 1.01], 31 | [2, 2, 2, 2, 1.00, 2.00], 32 | [3, 3, 3, 3, 1.00, 2.00], 33 | [4, 4, 4, 4, 1.00, 2.00], 34 | [5, 5, 5, 5, 1.00, 2.00]], dtype=float)) 35 | 36 | def test_ldist_asymmetric(self): 37 | ds = dtw(ldist, keep_internals=True, step_pattern=asymmetric) 38 | assert_equal(ds.distance, 2) 39 | assert_array_equal(ds.index1, np.arange(6)) 40 | assert_array_equal(ds.index2, i("1 3 5 5 5 6")) 41 | 42 | def test_ldist_asymmetricP0(self): 43 | ds = dtw(ldist, keep_internals=True, step_pattern=asymmetricP0) 44 | assert_equal(ds.distance, 1) 45 | assert_array_equal(ds.index1, i("1 2 2 2 2 3 4 5 6 6")) 46 | assert_array_equal(ds.index2, i("1 1 2 3 4 5 5 5 5 6")) 47 | 48 | def test_ldist_asymmetricP1(self): 49 | ds = dtw(ldist, keep_internals=True, step_pattern=asymmetricP1) 50 | assert_equal(ds.distance, 3) 51 | assert_array_equal(ds.index1, i("1 2 3 3 4 5 6")) 52 | assert_array_equal(ds.index1s, i("1 2 3 5 6")) 53 | assert_array_equal(ds.index2, i("1 2 3 4 5 5 6")) 54 | assert_array_equal(ds.index2s, i("1 2 4 5 6")) 55 | 56 | # Count paths is in another file 57 | 58 | def test_open_begin_end(self): 59 | query = np.arange(2, 4)+.01 60 | ref = np.arange(4)+1 61 | obe = dtw(query, ref, 62 | open_begin=True, open_end=True, 63 | step_pattern=asymmetric) 64 | assert_approx_equal(obe.distance, 0.02) 65 | assert_array_equal(obe.index2, i("2 3")) 66 | 67 | def test_cdist(self): 68 | from scipy.spatial.distance import cdist 69 | 70 | query = np.vstack([np.arange(1, 11), np.ones(10)]).T 71 | ref = np.vstack([np.arange(11, 16), 2*np.ones(5)]).T 72 | 73 | cxdist = cdist(query, ref, metric="cityblock") 74 | 75 | d1 = dtw(query, ref, dist_method="cityblock").distance 76 | d2 = dtw(cxdist).distance 77 | 78 | assert_approx_equal(d1, d2) 79 | -------------------------------------------------------------------------------- /tests/test_doctests.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import doctest 4 | import glob 5 | import os.path 6 | 7 | import dtw 8 | 9 | 10 | def run_suite(): 11 | md = dtw.__path__[0] 12 | fl = glob.glob(os.path.join(md, "*.py")) 13 | suite = doctest.DocFileSuite(*fl, module_relative=False) 14 | return unittest.TextTestRunner(verbosity=2).run(suite) 15 | 16 | 17 | class TestDoctests(unittest.TestCase): 18 | def test_doctests(self): 19 | r = run_suite() 20 | assert len(r.failures)==0 21 | 22 | 23 | if __name__ == "__main__": 24 | run_suite() 25 | -------------------------------------------------------------------------------- /tests/test_dtw.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | import numpy as np 5 | 6 | from dtw import * 7 | 8 | 9 | """ 10 | # As in the JSS paper 11 | 12 | ref <- window(aami3a, start = 0, end = 2) 13 | test <- window(aami3a, start = 2.7, end = 5) 14 | write.table(ref,"ref.dat",row.names=F,col.names=F) 15 | write.table(test,"test.dat",row.names=F,col.names=F) 16 | alignment <- dtw(test, ref, keep=T) 17 | alignment$distance 18 | """ 19 | 20 | """ 21 | 22 | warp> idx<-seq(0,6.28,len=100); 23 | 24 | warp> query<-sin(idx)+runif(100)/10; 25 | 26 | warp> reference<-cos(idx) 27 | 28 | warp> alignment<-dtw(query,reference); 29 | 30 | warp> wq<-warp(alignment,index.reference=FALSE); 31 | 32 | warp> wt<-warp(alignment,index.reference=TRUE); 33 | 34 | warp> old.par <- par(no.readonly = TRUE); 35 | 36 | warp> par(mfrow=c(2,1)); 37 | """ 38 | 39 | 40 | 41 | class TestDTW(unittest.TestCase): 42 | def test_sincos(self): 43 | idx = np.linspace(0,6.28,num=100) 44 | query = np.sin(idx) + np.random.uniform(size=100)/10.0 45 | reference = np.cos(idx) 46 | alignment = dtw(query,reference) 47 | 48 | -------------------------------------------------------------------------------- /tests/test_dtw_s.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy import nan 5 | from numpy.testing import (assert_approx_equal, 6 | assert_array_equal, assert_raises) 7 | from dtw import * 8 | 9 | 10 | class TestDTWs(unittest.TestCase): 11 | def test_matrix(self): 12 | dm = 10 * np.ones((4, 4)) + np.eye(4) 13 | al = dtw(dm, keep_internals=True) 14 | assert_array_equal(al.costMatrix, 15 | np.array([[11., 21., 31., 41.], 16 | [21., 32., 41., 51.], 17 | [31., 41., 52., 61.], 18 | [41., 51., 61., 72.]])) 19 | 20 | def test_rectangular(self): 21 | # Hand-checked 22 | x = np.array([1, 2, 3]) 23 | y = np.array([2, 3, 4, 5, 6]) 24 | al = dtw(x, y, keep_internals=True) 25 | assert_array_equal(al.costMatrix, 26 | np.array([[1., 3., 6., 10., 15.], 27 | [1., 2., 4., 7., 11.], 28 | [2., 1., 2., 4., 7.]])) 29 | assert_approx_equal(al.normalizedDistance,0.875) 30 | 31 | 32 | def test_backtrack(self): 33 | x = np.array([1, 2, 3]) 34 | y = np.array([2, 3, 4, 5, 6]) 35 | al = dtw(x, y) 36 | assert_array_equal(al.index1, np.array([0, 1, 2, 2, 2, 2])) 37 | assert_array_equal(al.index1s, np.array([0, 1, 2, 2, 2, 2])) 38 | assert_array_equal(al.index2, np.array([0, 0, 1, 2, 3, 4])) 39 | assert_array_equal(al.index2s, np.array([0, 0, 1, 2, 3, 4])) 40 | 41 | 42 | def test_vectors(self): 43 | x = np.array([1, 2, 3]) 44 | y = np.array([2, 3, 4]) 45 | al = dtw(x, y) 46 | assert_approx_equal(al.distance, 2.0) 47 | 48 | def test_asymmetric(self): 49 | lm = np.array([[1, 1, 2, 2, 3, 3], 50 | [1, 1, 1, 2, 2, 2], 51 | [3, 1, 2, 2, 3, 3], 52 | [3, 1, 2, 1, 1, 2], 53 | [3, 2, 1, 2, 1, 2], 54 | [3, 3, 3, 2, 1, 2]], dtype=np.double) 55 | alignment = dtw(lm, step_pattern=asymmetric, keep_internals=True) 56 | assert_array_equal(alignment.costMatrix, 57 | np.array([[1., nan, nan, nan, nan, nan], 58 | [2., 2., 2., nan, nan, nan], 59 | [5., 3., 4., 4., 5., nan], 60 | [8., 4., 5., 4., 5., 6.], 61 | [11., 6., 5., 6., 5., 6.], 62 | [14., 9., 8., 7., 6., 7.]]) 63 | ) 64 | 65 | def test_impossible(self): 66 | x = np.ones(4) 67 | y = np.ones(20) 68 | with assert_raises(ValueError): 69 | dtw(x, y, step_pattern=asymmetric) 70 | 71 | 72 | -------------------------------------------------------------------------------- /tests/test_issues.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import (assert_approx_equal, assert_equal, 5 | assert_array_equal) 6 | from dtw import * 7 | 8 | 9 | def i(l): 10 | return np.array([int(x) for x in l.split()], dtype=int)-1 11 | 12 | 13 | 14 | class TestIssues(unittest.TestCase): 15 | def test_issue_5(self): 16 | idx = np.linspace(0,6.28,num=100) 17 | query = np.sin(idx) 18 | 19 | idx1 = np.linspace(0,6.28,num=70) 20 | template = np.cos(idx1) + 0.5 21 | 22 | alignment = dtw(query, template, 23 | step_pattern=rabinerJuangStepPattern(ptype=4,slope_weighting="c"), 24 | keep_internals=True,open_end=False, open_begin=False) 25 | dist = alignment.distance 26 | test_index2 = alignment.index2 27 | 28 | assert_approx_equal(dist, 52.9795) 29 | 30 | ref_index2 = [0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 31 | 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 32 | 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 33 | 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 34 | 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 35 | 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 36 | 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, 45, 47, 37 | 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69] 38 | 39 | assert_array_equal(test_index2, ref_index2) 40 | 41 | assert_equal(len(test_index2), 100) 42 | --------------------------------------------------------------------------------