From 8f96cc107a9b47034c96c41412ade7e7b921199d Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Tue, 2 May 2023 19:53:26 +0000 Subject: [PATCH 001/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91d63311..be8c0f7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.6" +version = "v0.0.7" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From dda096dae47d7082910edb1f8b7f2d8f8ecd767b Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 2 May 2023 16:58:10 -0300 Subject: [PATCH 002/198] Releasing to pypi main repo --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 861cb2c9..e6e0812b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,11 +43,11 @@ jobs: - name: Build and publish to pypi uses: JRubics/poetry-publish@v1.16 with: - pypi_token: ${{ secrets.TEST_PYPI_TOKEN }} + pypi_token: ${{ secrets.PYPI_TOKEN }} python_version: "3.10" # following 2 lines set deployment to testpypi - repository_name: "testpypi" - repository_url: "https://test.pypi.org/legacy/" + # repository_name: "testpypi" + # repository_url: "https://test.pypi.org/legacy/" - name: Login to GitHub Container Registry uses: docker/login-action@v1 From 5f311066bc257d47edf0df29e93a105f657f6ee4 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Tue, 2 May 2023 20:07:35 +0000 Subject: [PATCH 003/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be8c0f7d..2bf881d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.7" +version = "v0.0.8" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 42e3b76e5f46d7eebf7a2ce9057ccc22b3b4a7f5 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 2 May 2023 17:11:58 -0300 Subject: [PATCH 004/198] Updating installation instructions of the README.md --- README.md | 43 ++++++++----------------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 04c75c24..c986dcc0 100644 --- a/README.md +++ b/README.md @@ -21,49 +21,22 @@ result.render_steady_state_amplitudes() ## Setup -Install `poetry` if it is not already on your system: +`neurotechdevkit` requires Python `>=3.9` and `<3.11` to be installed. You can find which Python version you have installed by running `python --version` in a terminal. If you don't have Python installed, or you are running an unsupported version, you can download it from [python.org](https://www.python.org/downloads/). Python environment managers like pyenv, conda, and poetry are all perfectly suitable as well. -```bash -$ curl -sSL https://install.python-poetry.org | python - -``` +You can install the package using: -Install research dependencies for the project either to the current virtual environment or a new one: - -```bash -$ poetry install +``` bash +pip install neurotechdevkit ``` -Install stride with -```bash -$ poetry run pip install git+https://github.com/trustimaging/stride +And then you must install stride using: +``` bash +pip install git+https://github.com/trustimaging/stride ``` ---- - -This will also install the `neurotechdevkit` package. - -If you are not using a virtual environment, `poetry` will [create one for you by default](https://python-poetry.org/docs/basic-usage/#using-your-virtual-environment). If you are already using a virtual environment, then `poetry` will install dependencies to that environment. - - -### Environment - -If you let poetry manage your virtual environment, you can use the environment in one of two ways: - -1. Activate the environment directly via: - ```bash - $ poetry shell - ``` -2. Prepend `poetry run` to any python command in order to run the command within the virtual environment. Example: - ```bash - $ poetry run foo - ``` - -If you are already using your own virtual environment, you should not need to change anything. ### Development -For development, the installation instructions are the same as listed above (poetry installs developer requirements by default). - -See our [contribution requirements](docs/contributing.md) for more information on how to contribute. +See our [contribution requirements](docs/contributing.md) for more information on how to install the package locally using poetry and on how to contribute. A Makefile is provided to assist with common commands such as linting and running unit tests. From 89a9678115f5b9201c8009dee09444bbeea54ed8 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Tue, 2 May 2023 20:12:58 +0000 Subject: [PATCH 005/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2bf881d7..73fde1b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.8" +version = "v0.0.9" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 11b4be8559d1a4dca250229250078ac149ff2bfe Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 2 May 2023 19:04:37 -0300 Subject: [PATCH 006/198] Adding stride dependencies to pyproject.toml --- poetry.lock | 1434 ++++++++++++++++++++++++++++++++++++++++-------- pyproject.toml | 29 + 2 files changed, 1227 insertions(+), 236 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89b0b95e..667d6970 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,6 +28,18 @@ files = [ dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"] +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + [[package]] name = "anyio" version = "3.6.2" @@ -300,6 +312,61 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] +[[package]] +name = "blinker" +version = "1.6.2" +description = "Fast, simple object-to-object and broadcast signaling" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, + {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, +] + +[[package]] +name = "blosc" +version = "1.11.1" +description = "Blosc data compressor" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blosc-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:345577759e5e1cd0905faf89ad1dbcc60bb7a56b759985a417c5b0cab0e0117a"}, + {file = "blosc-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8907404c3058ac4a767a0a8b279c4c5aaf90431b7238f5b9b464eebe50b91010"}, + {file = "blosc-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41578759a913122ba63c24750cb53f861a557a0946f5bb80ad62625e95c7f45a"}, + {file = "blosc-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bba4cfbc0a86788eb38acc05eb35ba97e2897441416c5f927f243ed7b4b406b6"}, + {file = "blosc-1.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:705df0302ba73293b6109800110994841d5de4491318a3b06c5bbcec32756939"}, + {file = "blosc-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bef546a039a1d984c6d2ed516ffcbc2a09dbbba61a0951c5197fad3f718095b1"}, + {file = "blosc-1.11.1-cp310-cp310-win32.whl", hash = "sha256:5d0ceff7a19f3c5c42d9d5ec13a90ae0b69cb2731e797a58ca0e3b25018cbb69"}, + {file = "blosc-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:f889715e28bbdc2f927a546f8debf403947cea1e3dd8f15e1428b66701416ee6"}, + {file = "blosc-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88c2eba2c9c013e44d5841660f7832425ebdc0bcfce9e11c59a8eb732f28cf9d"}, + {file = "blosc-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8321241c37f25ad4a21e3e0731246753b557dd8910eaee32bf7510eeee03648a"}, + {file = "blosc-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:417fb44fd50887e4b3e2f8fc91f350c791bf0e39a1b4d45a41071fc416fb767a"}, + {file = "blosc-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74c7bc804c608de4dc53c873f8ef5b481b9e3b0b6381e7d3fcff92683de9547"}, + {file = "blosc-1.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:94010a4fc094dda5a9abd9546e1f97af9a7dbd903a6eee39dd13244c7c419412"}, + {file = "blosc-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dfbb2bb0a3a69006ad0c627247b53e8ed5f844fc5cd5f09979f1e174f6dc4a6e"}, + {file = "blosc-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7b40a99add3120a26bc92e7a751a01e4aa854fc1835e0d12e9cf77dbf5498a2a"}, + {file = "blosc-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:751a4ad8b4696d1c54f05a5255fe911d72f424cd03a9ce97525d67f40946b2ea"}, + {file = "blosc-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf25b0aaf209411ad7ff65f42ee6c42e87d2a9161990743cb21ea264a4509e7b"}, + {file = "blosc-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2078f8d4500c83387a0e1c3f750c9a6d4848a80e1bf07ec1fd9c87bb1e2e2e92"}, + {file = "blosc-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:038ab5690c016a15cc5bf47977ae57b9cb4517f5df3eccd895c2d6c6656da6e6"}, + {file = "blosc-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e2283eb29c5938bfc63ae4091e0b3df5ef0492f83b9ffffa184532d1e5e7f8c"}, + {file = "blosc-1.11.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1bfc0a2cd115aa6eac75b841840b00fee0dd7bf77793650bdf470e12cf105627"}, + {file = "blosc-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:31ed162c9cc916c393eb9dada1beb632b11d4646afdf708c1c3f9d38402a4fab"}, + {file = "blosc-1.11.1-cp38-cp38-win32.whl", hash = "sha256:b113c900cfb47272b63f0dbee95016e24720d24d46009aa558e131128fb26aa5"}, + {file = "blosc-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:da627a51bc2947a698097ac0ccf2f6e51a3aed77f2d4297295365ef8a18d748e"}, + {file = "blosc-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:69cf3d2bc196befe4adddc3cca08315fd9e05208887b9fe031942e204d48ed49"}, + {file = "blosc-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:270cf6aa4da391950e0140cc9ee9ced8562e04d4eb1473fa42fc454b81eaeb80"}, + {file = "blosc-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24dfeafbbe0b4b6a028fcfd22c3920be738d6ef9e9d2ffcb2b66153f18ad455d"}, + {file = "blosc-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf07fccaa1d76fdb03d4eed641a71f2f54be1d1bffba9300bab4c8f437017b46"}, + {file = "blosc-1.11.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c793b3e33d025161bd3f8dcacc9bdfa011218eafbc607e195360456d3306ebf9"}, + {file = "blosc-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa129006fabeec63a1e54d30863e0a0330ec72caedbd5a5c282121796e84e1a0"}, + {file = "blosc-1.11.1-cp39-cp39-win32.whl", hash = "sha256:213ce859c89625c64776c8a59c6b235bca6dea81c185a273703366e733880ff5"}, + {file = "blosc-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c6371779ca9ec469735b05ee0a119cc431d72008f9f2813ca73d4acf2b39b2f"}, + {file = "blosc-1.11.1.tar.gz", hash = "sha256:c22119b27bae1063a697f639028b422d55811b0880c3fc0149cbdea791d0b276"}, +] + [[package]] name = "cached-property" version = "1.5.2" @@ -401,6 +468,18 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + [[package]] name = "cgen" version = "2020.1" @@ -666,63 +745,63 @@ test-no-images = ["pytest"] [[package]] name = "coverage" -version = "7.2.3" +version = "7.2.5" description = "Code coverage measurement for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, - {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, - {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, - {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, - {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, - {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, - {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, - {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, - {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, - {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, - {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, - {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, - {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, - {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, - {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, - {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, - {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, - {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, - {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, - {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, - {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, - {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, - {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, - {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, - {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, - {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, - {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, - {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, - {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, - {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, - {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, - {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, - {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, - {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, - {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, - {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, - {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, - {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, - {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, - {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, - {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, - {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, - {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, - {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, - {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, - {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, - {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, - {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, - {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, - {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, - {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, + {file = "coverage-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c"}, + {file = "coverage-7.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a"}, + {file = "coverage-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f"}, + {file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a"}, + {file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a"}, + {file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11"}, + {file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5"}, + {file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c"}, + {file = "coverage-7.2.5-cp310-cp310-win32.whl", hash = "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5"}, + {file = "coverage-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c"}, + {file = "coverage-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce"}, + {file = "coverage-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88"}, + {file = "coverage-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e"}, + {file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2"}, + {file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139"}, + {file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8"}, + {file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed"}, + {file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6"}, + {file = "coverage-7.2.5-cp311-cp311-win32.whl", hash = "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b"}, + {file = "coverage-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068"}, + {file = "coverage-7.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1"}, + {file = "coverage-7.2.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33"}, + {file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade"}, + {file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5"}, + {file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47"}, + {file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd"}, + {file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969"}, + {file = "coverage-7.2.5-cp37-cp37m-win32.whl", hash = "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718"}, + {file = "coverage-7.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0"}, + {file = "coverage-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84"}, + {file = "coverage-7.2.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790"}, + {file = "coverage-7.2.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771"}, + {file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045"}, + {file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614"}, + {file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3"}, + {file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd"}, + {file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1"}, + {file = "coverage-7.2.5-cp38-cp38-win32.whl", hash = "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813"}, + {file = "coverage-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212"}, + {file = "coverage-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b"}, + {file = "coverage-7.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200"}, + {file = "coverage-7.2.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5"}, + {file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e"}, + {file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303"}, + {file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3"}, + {file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a"}, + {file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1"}, + {file = "coverage-7.2.5-cp39-cp39-win32.whl", hash = "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31"}, + {file = "coverage-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252"}, + {file = "coverage-7.2.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3"}, + {file = "coverage-7.2.5.tar.gz", hash = "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47"}, ] [package.dependencies] @@ -743,33 +822,148 @@ files = [ {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, ] +[[package]] +name = "cython" +version = "0.29.34" +description = "The Cython compiler for writing C extensions for the Python language." +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.34-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:742544024ddb74314e2d597accdb747ed76bd126e61fcf49940a5b5be0a8f381"}, + {file = "Cython-0.29.34-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03daae07f8cbf797506446adae512c3dd86e7f27a62a541fa1ee254baf43e32c"}, + {file = "Cython-0.29.34-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a8de3e793a576e40ca9b4f5518610cd416273c7dc5e254115656b6e4ec70663"}, + {file = "Cython-0.29.34-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60969d38e6a456a67e7ef8ae20668eff54e32ba439d4068ccf2854a44275a30f"}, + {file = "Cython-0.29.34-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:21b88200620d80cfe193d199b259cdad2b9af56f916f0f7f474b5a3631ca0caa"}, + {file = "Cython-0.29.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:308c8f1e58bf5e6e8a1c4dcf8abbd2d13d0f9b1e582f4d9ae8b89857342d8bb5"}, + {file = "Cython-0.29.34-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d8f822fb6ecd5d88c42136561f82960612421154fc5bf23c57103a367bb91356"}, + {file = "Cython-0.29.34-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56866323f1660cecb4d5ff3a1fba92a56b91b7cfae0a8253777aa4bdb3bdf9a8"}, + {file = "Cython-0.29.34-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e971db8aeb12e7c0697cefafe65eefcc33ff1224ae3d8c7f83346cbc42c6c270"}, + {file = "Cython-0.29.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4401270b0dc464c23671e2e9d52a60985f988318febaf51b047190e855bbe7d"}, + {file = "Cython-0.29.34-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:dce0a36d163c05ae8b21200059511217d79b47baf2b7b0f926e8367bd7a3cc24"}, + {file = "Cython-0.29.34-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dbd79221869ee9a6ccc4953b2c8838bb6ae08ab4d50ea4b60d7894f03739417b"}, + {file = "Cython-0.29.34-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a0f4229df10bc4545ebbeaaf96ebb706011d8b333e54ed202beb03f2bee0a50e"}, + {file = "Cython-0.29.34-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd1ea21f1cebf33ae288caa0f3e9b5563a709f4df8925d53bad99be693fc0d9b"}, + {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d7ef5f68f4c5baa93349ea54a352f8716d18bee9a37f3e93eff38a5d4e9b7262"}, + {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:459994d1de0f99bb18fad9f2325f760c4b392b1324aef37bcc1cd94922dfce41"}, + {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1d6c809e2f9ce5950bbc52a1d2352ef3d4fc56186b64cb0d50c8c5a3c1d17661"}, + {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f674ceb5f722d364395f180fbac273072fc1a266aab924acc9cfd5afc645aae1"}, + {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9489de5b2044dcdfd9d6ca8242a02d560137b3c41b1f5ae1c4f6707d66d6e44d"}, + {file = "Cython-0.29.34-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5c121dc185040f4333bfded68963b4529698e1b6d994da56be32c97a90c896b6"}, + {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b6149f7cc5b31bccb158c5b968e5a8d374fdc629792e7b928a9b66e08b03fca5"}, + {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0ab3cbf3d62b0354631a45dc93cfcdf79098663b1c65a6033af4a452b52217a7"}, + {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:4a2723447d1334484681d5aede34184f2da66317891f94b80e693a2f96a8f1a7"}, + {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e40cf86aadc29ecd1cb6de67b0d9488705865deea4fc185c7ad56d7a6fc78703"}, + {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8c3cd8bb8e880a3346f5685601004d96e0a2221e73edcaeea57ea848618b4ac6"}, + {file = "Cython-0.29.34-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0e9032cd650b0cb1d2c2ef2623f5714c14d14c28d7647d589c3eeed0baf7428e"}, + {file = "Cython-0.29.34-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bdb3285660e3068438791ace7dd7b1efd6b442a10b5c8d7a4f0c9d184d08c8ed"}, + {file = "Cython-0.29.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a8ad755f9364e720f10a36734a1c7a5ced5c679446718b589259261438a517c9"}, + {file = "Cython-0.29.34-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7595d29eaee95633dd8060f50f0e54b27472d01587659557ebcfe39da3ea946b"}, + {file = "Cython-0.29.34-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ef7879668214d80ea3914c17e7d4e1ebf4242e0dd4dabe95ca5ccbe75589a5"}, + {file = "Cython-0.29.34-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ccb223b5f0fd95d8d27561efc0c14502c0945f1a32274835831efa5d5baddfc1"}, + {file = "Cython-0.29.34-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:11b1b278b8edef215caaa5250ad65a10023bfa0b5a93c776552248fc6f60098d"}, + {file = "Cython-0.29.34-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5718319a01489688fdd22ddebb8e2fcbbd60be5f30de4336ea7063c3ae29fbe5"}, + {file = "Cython-0.29.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:cfb2302ef617d647ee590a4c0a00ba3c2da05f301dcefe7721125565d2e51351"}, + {file = "Cython-0.29.34-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:67b850cf46b861bc27226d31e1d87c0e69869a02f8d3cc5d5bef549764029879"}, + {file = "Cython-0.29.34-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0963266dad685812c1dbb758fcd4de78290e3adc7db271c8664dcde27380b13e"}, + {file = "Cython-0.29.34-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7879992487d9060a61393eeefe00d299210256928dce44d887b6be313d342bac"}, + {file = "Cython-0.29.34-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:44733366f1604b0c327613b6918469284878d2f5084297d10d26072fc6948d51"}, + {file = "Cython-0.29.34-py2.py3-none-any.whl", hash = "sha256:be4f6b7be75a201c290c8611c0978549c60353890204573078e865423dbe3c83"}, + {file = "Cython-0.29.34.tar.gz", hash = "sha256:1909688f5d7b521a60c396d20bba9e47a1b2d2784bfb085401e1e1e7d29a29a8"}, +] + +[[package]] +name = "dash" +version = "2.9.3" +description = "A Python framework for building reactive web-apps. Developed by Plotly." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "dash-2.9.3-py3-none-any.whl", hash = "sha256:a749ae1ea9de3fe7b785353a818ec9b629d39c6b7e02462954203bd1e296fd0e"}, + {file = "dash-2.9.3.tar.gz", hash = "sha256:47392f8d6455dc989a697407eb5941f3bad80604df985ab1ac9d4244568ffb34"}, +] + +[package.dependencies] +dash-core-components = "2.0.0" +dash-html-components = "2.0.0" +dash-table = "5.0.0" +Flask = ">=1.0.4" +plotly = ">=5.0.0" + +[package.extras] +celery = ["celery[redis] (>=5.1.2)", "importlib-metadata (<5)", "redis (>=3.5.3)"] +ci = ["black (==21.6b0)", "black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==3.9.2)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "isort (==4.3.21)", "mimesis", "mock (==4.0.3)", "numpy", "openpyxl", "orjson (==3.5.4)", "orjson (==3.6.7)", "pandas (==1.1.5)", "pandas (>=1.4.0)", "preconditions", "pyarrow", "pyarrow (<3)", "pylint (==2.13.5)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "xlrd (<2)", "xlrd (>=2.0.1)"] +compress = ["flask-compress"] +dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"] +diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] +testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4)", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"] + +[[package]] +name = "dash-core-components" +version = "2.0.0" +description = "Core component suite for Dash" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, + {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, +] + +[[package]] +name = "dash-html-components" +version = "2.0.0" +description = "Vanilla HTML components for Dash" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, + {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, +] + +[[package]] +name = "dash-table" +version = "5.0.0" +description = "Dash table" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, + {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, +] + [[package]] name = "dask" -version = "2022.6.1" +version = "2023.3.2" description = "Parallel PyData with Task Scheduling" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "dask-2022.6.1-py3-none-any.whl", hash = "sha256:fbd2707070ee8cba080a297301c3984de3d0dee211f30c81387d5fa8adc7de74"}, - {file = "dask-2022.6.1.tar.gz", hash = "sha256:6ecc4571da3e5575dd1fa1537237434908f81b929f7f2a3493a7f6eb8507903e"}, + {file = "dask-2023.3.2-py3-none-any.whl", hash = "sha256:5e64763d62feb18afd3ad66f364e0b4f456f7ac92e894fcc87950af75029ecdf"}, + {file = "dask-2023.3.2.tar.gz", hash = "sha256:51009e92ba9a280bd417633d1ae84f3ed23a8940f0a19594a4b7797ef226fff4"}, ] [package.dependencies] +click = ">=7.0" cloudpickle = ">=1.1.1" fsspec = ">=0.6.0" +importlib-metadata = ">=4.13.0" packaging = ">=20.0" -partd = ">=0.3.10" +partd = ">=1.2.0" pyyaml = ">=5.3.1" toolz = ">=0.8.2" [package.extras] -array = ["numpy (>=1.18)"] -complete = ["bokeh (>=2.4.2)", "distributed (==2022.6.1)", "jinja2", "numpy (>=1.18)", "pandas (>=1.0)"] -dataframe = ["numpy (>=1.18)", "pandas (>=1.0)"] -diagnostics = ["bokeh (>=2.4.2)", "jinja2"] -distributed = ["distributed (==2022.6.1)"] -test = ["pre-commit", "pytest", "pytest-rerunfailures", "pytest-xdist"] +array = ["numpy (>=1.21)"] +complete = ["bokeh (>=2.4.2,<3)", "distributed (==2023.3.2)", "jinja2 (>=2.10.3)", "lz4 (>=4.3.2)", "numpy (>=1.21)", "pandas (>=1.3)", "pyarrow (>=7.0)"] +dataframe = ["numpy (>=1.21)", "pandas (>=1.3)"] +diagnostics = ["bokeh (>=2.4.2,<3)", "jinja2 (>=2.10.3)"] +distributed = ["distributed (==2023.3.2)"] +test = ["pandas[test]", "pre-commit", "pytest", "pytest-rerunfailures", "pytest-xdist"] [[package]] name = "debugpy" @@ -861,34 +1055,73 @@ extras = ["matplotlib", "pandas"] mpi = ["ipyparallel (<8.6)", "mpi4py (<4.0)"] nvidia = ["cupy-cuda110", "dask-cuda", "dask-labextension", "fsspec", "jupyterlab (>=3)", "jupyterlab-nvdashboard"] +[[package]] +name = "dill" +version = "0.3.6" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, + {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + [[package]] name = "distributed" -version = "2022.6.1" +version = "2023.3.2.1" description = "Distributed scheduler for Dask" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "distributed-2022.6.1-py3-none-any.whl", hash = "sha256:4c614050e86ac8a0230418625f313a71dffdcd9cfb3b7128a33b7faa611a686c"}, - {file = "distributed-2022.6.1.tar.gz", hash = "sha256:c4efd70aeb072725e683bca1c2de8173cd577018a7a5c3795886a5691e70bc37"}, + {file = "distributed-2023.3.2.1-py3-none-any.whl", hash = "sha256:a7756a4b952ec5a7fd3163e93aef99aaf8b0000568fa9ee7c000113a470d7f8e"}, + {file = "distributed-2023.3.2.1.tar.gz", hash = "sha256:f40c4578622a15261bb59676ca8d7024f7d108aecc58406a5482bdfb7e69ce99"}, ] [package.dependencies] -click = ">=6.6" +click = ">=8.0" cloudpickle = ">=1.5.0" -dask = "2022.6.1" -jinja2 = "*" +dask = "2023.3.2" +jinja2 = ">=2.10.3" locket = ">=1.0.0" -msgpack = ">=0.6.0" +msgpack = ">=1.0.0" packaging = ">=20.0" -psutil = ">=5.0" -pyyaml = "*" -sortedcontainers = "<2.0.0 || >2.0.0,<2.0.1 || >2.0.1" +psutil = ">=5.7.0" +pyyaml = ">=5.3.1" +sortedcontainers = ">=2.0.5" tblib = ">=1.6.0" -toolz = ">=0.8.2" +toolz = ">=0.10.0" tornado = ">=6.0.3" -urllib3 = "*" -zict = ">=0.1.3" +urllib3 = ">=1.24.3" +zict = ">=2.1.0" + +[[package]] +name = "docutils" +version = "0.18.1" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, +] [[package]] name = "exceptiongroup" @@ -935,6 +1168,22 @@ files = [ [package.extras] devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] +[[package]] +name = "filelock" +version = "3.12.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, + {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, +] + +[package.extras] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + [[package]] name = "flake8" version = "5.0.4" @@ -952,6 +1201,30 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" +[[package]] +name = "flask" +version = "2.3.2" +description = "A simple framework for building complex web applications." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, + {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=2.3.3" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + [[package]] name = "fonttools" version = "4.39.3" @@ -1128,6 +1401,17 @@ python-dateutil = ">=2.8.1" [package.extras] dev = ["flake8", "markdown", "twine", "wheel"] +[[package]] +name = "gputil" +version = "1.4.0" +description = "GPUtil is a Python module for getting the GPU status from NVIDA GPUs using nvidia-smi." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "GPUtil-1.4.0.tar.gz", hash = "sha256:099e52c65e512cdfa8c8763fca67f5a5c2afb63469602d5dcb4d296b3661efb9"}, +] + [[package]] name = "griffe" version = "0.27.1" @@ -1200,6 +1484,21 @@ files = [ h5py = {version = ">=2.1", markers = "python_version >= \"3.3\""} numpy = {version = "*", markers = "python_version >= \"3.4\""} +[[package]] +name = "identify" +version = "2.5.23" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.23-py2.py3-none-any.whl", hash = "sha256:17d9351c028a781456965e781ed2a435755cac655df1ebd930f7186b54399312"}, + {file = "identify-2.5.23.tar.gz", hash = "sha256:50b01b9d5f73c6b53e5fa2caf9f543d3e657a9d0bbdeb203ebb8d45960ba7433"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.4" @@ -1214,14 +1513,14 @@ files = [ [[package]] name = "imageio" -version = "2.27.0" +version = "2.28.1" description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "imageio-2.27.0-py3-none-any.whl", hash = "sha256:24c6ad7d000e64eacc2861c402b6fb128f370cb0a6623cf796d83bca0d0d14d3"}, - {file = "imageio-2.27.0.tar.gz", hash = "sha256:ee269c957785ef0373cc7a7323185956d83ec05e6cdf20b42a03ba7b74ac58c6"}, + {file = "imageio-2.28.1-py3-none-any.whl", hash = "sha256:b9b456146aab459e648cde633b81bf487eb45248948f79c033e55af3bf1e6d70"}, + {file = "imageio-2.28.1.tar.gz", hash = "sha256:5db5087be5c814ecf7e2c7d30a1a15c97eca97d8c26f31ddc54d767d4a43bce8"}, ] [package.dependencies] @@ -1232,28 +1531,40 @@ pillow = ">=8.3.2" all-plugins = ["astropy", "av", "imageio-ffmpeg", "psutil", "tifffile"] all-plugins-pypy = ["av", "imageio-ffmpeg", "psutil", "tifffile"] build = ["wheel"] -dev = ["black", "flake8", "fsspec[github]", "invoke", "pytest", "pytest-cov"] +dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] ffmpeg = ["imageio-ffmpeg", "psutil"] fits = ["astropy"] -full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "invoke", "itk", "numpydoc", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx (<6)", "tifffile", "wheel"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpydoc", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx (<6)", "tifffile", "wheel"] gdal = ["gdal"] itk = ["itk"] linting = ["black", "flake8"] pyav = ["av"] -test = ["fsspec[github]", "invoke", "pytest", "pytest-cov"] +test = ["fsspec[github]", "pytest", "pytest-cov"] tifffile = ["tifffile"] +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "importlib-metadata" -version = "6.5.0" +version = "6.6.0" description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.5.0-py3-none-any.whl", hash = "sha256:03ba783c3a2c69d751b109fc0c94a62c51f581b3d6acf8ed1331b6d5729321ff"}, - {file = "importlib_metadata-6.5.0.tar.gz", hash = "sha256:7a8bdf1bc3a726297f5cfbc999e6e7ff6b4fa41b26bba4afc580448624460045"}, + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, ] [package.dependencies] @@ -1331,14 +1642,14 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio" [[package]] name = "ipython" -version = "8.12.0" +version = "8.13.1" description = "IPython: Productive Interactive Computing" category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "ipython-8.12.0-py3-none-any.whl", hash = "sha256:1c183bf61b148b00bcebfa5d9b39312733ae97f6dad90d7e9b4d86c8647f498c"}, - {file = "ipython-8.12.0.tar.gz", hash = "sha256:a950236df04ad75b5bc7f816f9af3d74dc118fd42f2ff7e80e8e60ca1f182e2d"}, + {file = "ipython-8.13.1-py3-none-any.whl", hash = "sha256:1c80d08f04144a1994cda25569eab07fbdc4989bd8d8793e3a4ff643065ccb51"}, + {file = "ipython-8.13.1.tar.gz", hash = "sha256:9c8487ac18f330c8a683fc50ab6d7bc0fcf9ef1d7a9f6ce7926938261067b81f"}, ] [package.dependencies] @@ -1414,6 +1725,18 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + [[package]] name = "jedi" version = "0.18.2" @@ -1839,6 +2162,22 @@ files = [ {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, ] +[[package]] +name = "lazy-loader" +version = "0.2" +description = "lazy_loader" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy_loader-0.2-py3-none-any.whl", hash = "sha256:c35875f815c340f823ce3271ed645045397213f961b40ad0c0d395c3f5218eeb"}, + {file = "lazy_loader-0.2.tar.gz", hash = "sha256:0edc7a5175c400acb108f283749951fefdadedeb00adcec6e88b974a9254f18a"}, +] + +[package.extras] +lint = ["pre-commit (>=3.1)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + [[package]] name = "locket" version = "1.0.0" @@ -1851,6 +2190,18 @@ files = [ {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, ] +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] + [[package]] name = "markdown" version = "3.3.7" @@ -2045,14 +2396,14 @@ files = [ [[package]] name = "mkdocs" -version = "1.4.2" +version = "1.4.3" description = "Project documentation with Markdown." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, - {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, + {file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"}, + {file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"}, ] [package.dependencies] @@ -2090,14 +2441,14 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-gallery" -version = "0.7.6" +version = "0.7.7" description = "a `mkdocs` plugin to generate example galleries from python scripts, similar to `sphinx-gallery`." category = "dev" optional = false python-versions = "*" files = [ - {file = "mkdocs-gallery-0.7.6.tar.gz", hash = "sha256:cf6cd5b548c4c7112ebea892462d773c1afe9d3114be226e38509ea0a0dafde8"}, - {file = "mkdocs_gallery-0.7.6-py2.py3-none-any.whl", hash = "sha256:69e9c87714e8ec461343e8516ad9938ce080360702f16bcd06754fda7cb9e5ca"}, + {file = "mkdocs-gallery-0.7.7.tar.gz", hash = "sha256:c9758ba30eef437708d6e12a417f0d56f24aeeb194d664d5dbf7c394f6ddc58c"}, + {file = "mkdocs_gallery-0.7.7-py2.py3-none-any.whl", hash = "sha256:f0867751780d625c9b56bffd87174e77d1458acc87765254344e952f19b3025b"}, ] [package.dependencies] @@ -2107,14 +2458,14 @@ tqdm = "*" [[package]] name = "mkdocs-material" -version = "9.1.6" +version = "9.1.9" description = "Documentation that simply works" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-9.1.6-py3-none-any.whl", hash = "sha256:f2eb1d40db89da9922944833c1387207408f8937e1c2b46ab86e0c8f170b71e0"}, - {file = "mkdocs_material-9.1.6.tar.gz", hash = "sha256:2e555152f9771646bfa62dc78a86052876183eff69ce30db03a33e85702b21fc"}, + {file = "mkdocs_material-9.1.9-py3-none-any.whl", hash = "sha256:7db24261cb17400e132c46d17eea712bfe71056d892a9beba32cf68210297141"}, + {file = "mkdocs_material-9.1.9.tar.gz", hash = "sha256:74d8da1371ab3a326868fe47bae3cbc4aa22e93c048b4ca5117e6817b88bd734"}, ] [package.dependencies] @@ -2358,6 +2709,33 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "multiprocess" +version = "0.70.14" +description = "better multiprocessing and multithreading in python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multiprocess-0.70.14-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560a27540daef4ce8b24ed3cc2496a3c670df66c96d02461a4da67473685adf3"}, + {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:bfbbfa36f400b81d1978c940616bc77776424e5e34cb0c94974b178d727cfcd5"}, + {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:89fed99553a04ec4f9067031f83a886d7fdec5952005551a896a4b6a59575bb9"}, + {file = "multiprocess-0.70.14-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:40a5e3685462079e5fdee7c6789e3ef270595e1755199f0d50685e72523e1d2a"}, + {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_i686.whl", hash = "sha256:44936b2978d3f2648727b3eaeab6d7fa0bedf072dc5207bf35a96d5ee7c004cf"}, + {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e628503187b5d494bf29ffc52d3e1e57bb770ce7ce05d67c4bbdb3a0c7d3b05f"}, + {file = "multiprocess-0.70.14-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0d5da0fc84aacb0e4bd69c41b31edbf71b39fe2fb32a54eaedcaea241050855c"}, + {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_i686.whl", hash = "sha256:6a7b03a5b98e911a7785b9116805bd782815c5e2bd6c91c6a320f26fd3e7b7ad"}, + {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:cea5bdedd10aace3c660fedeac8b087136b4366d4ee49a30f1ebf7409bce00ae"}, + {file = "multiprocess-0.70.14-py310-none-any.whl", hash = "sha256:7dc1f2f6a1d34894c8a9a013fbc807971e336e7cc3f3ff233e61b9dc679b3b5c"}, + {file = "multiprocess-0.70.14-py37-none-any.whl", hash = "sha256:93a8208ca0926d05cdbb5b9250a604c401bed677579e96c14da3090beb798193"}, + {file = "multiprocess-0.70.14-py38-none-any.whl", hash = "sha256:6725bc79666bbd29a73ca148a0fb5f4ea22eed4a8f22fce58296492a02d18a7b"}, + {file = "multiprocess-0.70.14-py39-none-any.whl", hash = "sha256:63cee628b74a2c0631ef15da5534c8aedbc10c38910b9c8b18dcd327528d1ec7"}, + {file = "multiprocess-0.70.14.tar.gz", hash = "sha256:3eddafc12f2260d27ae03fe6069b12570ab4764ab59a75e81624fac453fbf46a"}, +] + +[package.dependencies] +dill = ">=0.3.6" + [[package]] name = "mypy" version = "0.990" @@ -2423,14 +2801,14 @@ files = [ [[package]] name = "nbclassic" -version = "0.5.5" +version = "0.5.6" description = "Jupyter Notebook as a Jupyter Server extension." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "nbclassic-0.5.5-py3-none-any.whl", hash = "sha256:47791b04dbcb89bf7fde910a3d848fd4793a4248a8936202453631a87da37d51"}, - {file = "nbclassic-0.5.5.tar.gz", hash = "sha256:d2c91adc7909b0270c73e3e253d3687a6704b4f0a94bc156a37c85eba09f4d37"}, + {file = "nbclassic-0.5.6-py3-none-any.whl", hash = "sha256:e3c8b7de80046c4a36a74662a5e325386d345289906c618366d8154e03dc2322"}, + {file = "nbclassic-0.5.6.tar.gz", hash = "sha256:aab53fa1bea084fb6ade5c538b011a4f070c69f88d72878a8e8fb356f152509f"}, ] [package.dependencies] @@ -2444,7 +2822,7 @@ jupyter-server = ">=1.8" nbconvert = ">=5" nbformat = "*" nest-asyncio = ">=1.5" -notebook-shim = ">=0.1.0" +notebook-shim = ">=0.2.3" prometheus-client = "*" pyzmq = ">=17" Send2Trash = ">=1.8.0" @@ -2459,14 +2837,14 @@ test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-p [[package]] name = "nbclient" -version = "0.7.3" +version = "0.7.4" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "nbclient-0.7.3-py3-none-any.whl", hash = "sha256:8fa96f7e36693d5e83408f5e840f113c14a45c279befe609904dbe05dad646d1"}, - {file = "nbclient-0.7.3.tar.gz", hash = "sha256:26e41c6dca4d76701988bc34f64e1bfc2413ae6d368f13d7b5ac407efb08c755"}, + {file = "nbclient-0.7.4-py3-none-any.whl", hash = "sha256:c817c0768c5ff0d60e468e017613e6eae27b6fa31e43f905addd2d24df60c125"}, + {file = "nbclient-0.7.4.tar.gz", hash = "sha256:d447f0e5a4cfe79d462459aec1b3dc5c2e9152597262be8ee27f7d4c02566a0d"}, ] [package.dependencies] @@ -2572,6 +2950,40 @@ files = [ {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, ] +[[package]] +name = "networkx" +version = "3.1" +description = "Python package for creating and manipulating graphs and networks" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, + {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, +] + +[package.extras] +default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "notebook" version = "6.5.4" @@ -2609,58 +3021,58 @@ test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixs [[package]] name = "notebook-shim" -version = "0.2.2" +version = "0.2.3" description = "A shim layer for notebook traits and config" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "notebook_shim-0.2.2-py3-none-any.whl", hash = "sha256:9c6c30f74c4fbea6fce55c1be58e7fd0409b1c681b075dcedceb005db5026949"}, - {file = "notebook_shim-0.2.2.tar.gz", hash = "sha256:090e0baf9a5582ff59b607af523ca2db68ff216da0c69956b62cab2ef4fc9c3f"}, + {file = "notebook_shim-0.2.3-py3-none-any.whl", hash = "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7"}, + {file = "notebook_shim-0.2.3.tar.gz", hash = "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"}, ] [package.dependencies] jupyter-server = ">=1.8,<3" [package.extras] -test = ["pytest", "pytest-console-scripts", "pytest-tornasync"] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] [[package]] name = "numpy" -version = "1.24.2" +version = "1.24.3" description = "Fundamental package for array computing in Python" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"}, - {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"}, - {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"}, - {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"}, - {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"}, - {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"}, - {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"}, - {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"}, - {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"}, - {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"}, - {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"}, - {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"}, - {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"}, - {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"}, - {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"}, - {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"}, - {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"}, - {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"}, - {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"}, - {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"}, - {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"}, - {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"}, - {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"}, - {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"}, - {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, ] [[package]] @@ -2843,31 +3255,47 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "pip" -version = "23.1" +version = "23.1.2" description = "The PyPA recommended tool for installing Python packages." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pip-23.1-py3-none-any.whl", hash = "sha256:64b1d4528e491aa835ec6ece0c1ac40ce6ab6d886e60740f6519db44b2e9634d"}, - {file = "pip-23.1.tar.gz", hash = "sha256:408539897ee535dbfb83a153f7bc4d620f990d8bd44a52a986efc0b4d330d34a"}, + {file = "pip-23.1.2-py3-none-any.whl", hash = "sha256:3ef6ac33239e4027d9a5598a381b9d30880a1477e50039db2eac6e8a8f6d1b18"}, + {file = "pip-23.1.2.tar.gz", hash = "sha256:0e7c86f486935893c708287b30bd050a36ac827ec7fe5e43fe7cb198dd835fba"}, ] [[package]] name = "platformdirs" -version = "3.2.0" +version = "3.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"}, - {file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"}, + {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, + {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "plotly" +version = "5.14.1" +description = "An open-source, interactive data visualization library for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "plotly-5.14.1-py2.py3-none-any.whl", hash = "sha256:a63f3ad9e4cc2e02902a738e5e3e7f3d1307f2732ac71a6c28f1238ed3052826"}, + {file = "plotly-5.14.1.tar.gz", hash = "sha256:bcac86d7fcba3eff7260c1eddc36ca34dae2aded10a0709808446565e0e53b93"}, +] + +[package.dependencies] +packaging = "*" +tenacity = ">=6.2.0" [[package]] name = "pluggy" @@ -2885,6 +3313,25 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.3.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.3.1-py2.py3-none-any.whl", hash = "sha256:218e9e3f7f7f3271ebc355a15598a4d3893ad9fc7b57fe446db75644543323b9"}, + {file = "pre_commit-3.3.1.tar.gz", hash = "sha256:733f78c9a056cdd169baa6cd4272d51ecfda95346ef8a89bf93712706021b907"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "prometheus-client" version = "0.16.0" @@ -2971,13 +3418,26 @@ tests = ["pytest"] [[package]] name = "py-cpuinfo" -version = "8.0.0" -description = "Get CPU info with pure Python 2 & 3" +version = "9.0.0" +description = "Get CPU info with pure Python" category = "main" optional = false python-versions = "*" files = [ - {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, + {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, + {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, +] + +[[package]] +name = "py-libnuma" +version = "1.2" +description = "Python3 libnuma ctypes wrapper" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "py-libnuma-1.2.tar.gz", hash = "sha256:e9d67ad04f274015e9acac497d642ce92f8a96d1d4e7be24e39124b24320b95a"}, + {file = "py_libnuma-1.2-py3-none-any.whl", hash = "sha256:a4c0b9188c03a1ba23b994e9c99f67ef25a090fadce90db94d46e229bd258fb9"}, ] [[package]] @@ -3193,6 +3653,27 @@ files = [ docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-virtualenv", "types-setuptools"] +[[package]] +name = "python-daemon" +version = "3.0.1" +description = "Library to implement a well-behaved Unix daemon process." +category = "dev" +optional = false +python-versions = ">=3" +files = [ + {file = "python-daemon-3.0.1.tar.gz", hash = "sha256:6c57452372f7eaff40934a1c03ad1826bf5e793558e87fef49131e6464b4dae5"}, + {file = "python_daemon-3.0.1-py3-none-any.whl", hash = "sha256:42bb848a3260a027fa71ad47ecd959e471327cb34da5965962edd5926229f341"}, +] + +[package.dependencies] +docutils = "*" +lockfile = ">=0.10" +setuptools = ">=62.4.0" + +[package.extras] +devel = ["coverage", "docutils", "isort", "testscenarios (>=0.4)", "testtools", "twine"] +test = ["coverage", "docutils", "testscenarios (>=0.4)", "testtools"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -3238,6 +3719,44 @@ typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] numpy = ["numpy (>=1.6.0)"] +[[package]] +name = "pywavelets" +version = "1.4.1" +description = "PyWavelets, wavelet transform module" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c"}, + {file = "PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4"}, + {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c"}, + {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202"}, + {file = "PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd"}, + {file = "PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b"}, + {file = "PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875"}, + {file = "PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de"}, + {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e"}, + {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784"}, + {file = "PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1"}, + {file = "PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc"}, + {file = "PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966"}, + {file = "PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa"}, + {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc"}, + {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4"}, + {file = "PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd"}, + {file = "PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2"}, + {file = "PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6"}, + {file = "PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426"}, + {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b"}, + {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356"}, + {file = "PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c"}, + {file = "PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4"}, + {file = "PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93"}, +] + +[package.dependencies] +numpy = ">=1.17.3" + [[package]] name = "pywin32" version = "306" @@ -3435,84 +3954,112 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "regex" -version = "2023.3.23" +version = "2023.5.4" description = "Alternative regular expression module, to replace re." category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" files = [ - {file = "regex-2023.3.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:845a5e2d84389c4ddada1a9b95c055320070f18bb76512608374aca00d22eca8"}, - {file = "regex-2023.3.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87d9951f5a538dd1d016bdc0dcae59241d15fa94860964833a54d18197fcd134"}, - {file = "regex-2023.3.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae17d3be44c0b3f782c28ae9edd8b47c1f1776d4cabe87edc0b98e1f12b021"}, - {file = "regex-2023.3.23-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b8eb1e3bca6b48dc721818a60ae83b8264d4089a4a41d62be6d05316ec38e15"}, - {file = "regex-2023.3.23-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df45fac182ebc3c494460c644e853515cc24f5ad9da05f8ffb91da891bfee879"}, - {file = "regex-2023.3.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7006105b10b59971d3b248ad75acc3651c7e4cf54d81694df5a5130a3c3f7ea"}, - {file = "regex-2023.3.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93f3f1aa608380fe294aa4cb82e2afda07a7598e828d0341e124b8fd9327c715"}, - {file = "regex-2023.3.23-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787954f541ab95d8195d97b0b8cf1dc304424adb1e07365967e656b92b38a699"}, - {file = "regex-2023.3.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:20abe0bdf03630fe92ccafc45a599bca8b3501f48d1de4f7d121153350a2f77d"}, - {file = "regex-2023.3.23-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11d00c31aeab9a6e0503bc77e73ed9f4527b3984279d997eb145d7c7be6268fd"}, - {file = "regex-2023.3.23-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d5bbe0e1511b844794a3be43d6c145001626ba9a6c1db8f84bdc724e91131d9d"}, - {file = "regex-2023.3.23-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ea3c0cb56eadbf4ab2277e7a095676370b3e46dbfc74d5c383bd87b0d6317910"}, - {file = "regex-2023.3.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d895b4c863059a4934d3e874b90998df774644a41b349ebb330f85f11b4ef2c0"}, - {file = "regex-2023.3.23-cp310-cp310-win32.whl", hash = "sha256:9d764514d19b4edcc75fd8cb1423448ef393e8b6cbd94f38cab983ab1b75855d"}, - {file = "regex-2023.3.23-cp310-cp310-win_amd64.whl", hash = "sha256:11d1f2b7a0696dc0310de0efb51b1f4d813ad4401fe368e83c0c62f344429f98"}, - {file = "regex-2023.3.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a9c63cde0eaa345795c0fdeb19dc62d22e378c50b0bc67bf4667cd5b482d98b"}, - {file = "regex-2023.3.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dd7200b4c27b68cf9c9646da01647141c6db09f48cc5b51bc588deaf8e98a797"}, - {file = "regex-2023.3.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22720024b90a6ba673a725dcc62e10fb1111b889305d7c6b887ac7466b74bedb"}, - {file = "regex-2023.3.23-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b190a339090e6af25f4a5fd9e77591f6d911cc7b96ecbb2114890b061be0ac1"}, - {file = "regex-2023.3.23-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e76b6fc0d8e9efa39100369a9b3379ce35e20f6c75365653cf58d282ad290f6f"}, - {file = "regex-2023.3.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b8f218bf69a2a15402fde08b08712213a1f4b85a156d90473a6fb6b12b09"}, - {file = "regex-2023.3.23-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2472428efc4127374f494e570e36b30bb5e6b37d9a754f7667f7073e43b0abdd"}, - {file = "regex-2023.3.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c37df2a060cb476d94c047b18572ee2b37c31f831df126c0da3cd9227b39253d"}, - {file = "regex-2023.3.23-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4479f9e2abc03362df4045b1332d4a2b7885b245a30d4f4b051c4083b97d95d8"}, - {file = "regex-2023.3.23-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2396e0678167f2d0c197da942b0b3fb48fee2f0b5915a0feb84d11b6686afe6"}, - {file = "regex-2023.3.23-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:75f288c60232a5339e0ff2fa05779a5e9c74e9fc085c81e931d4a264501e745b"}, - {file = "regex-2023.3.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c869260aa62cee21c5eb171a466c0572b5e809213612ef8d495268cd2e34f20d"}, - {file = "regex-2023.3.23-cp311-cp311-win32.whl", hash = "sha256:25f0532fd0c53e96bad84664171969de9673b4131f2297f1db850d3918d58858"}, - {file = "regex-2023.3.23-cp311-cp311-win_amd64.whl", hash = "sha256:5ccfafd98473e007cebf7da10c1411035b7844f0f204015efd050601906dbb53"}, - {file = "regex-2023.3.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6572ff287176c0fb96568adb292674b421fa762153ed074d94b1d939ed92c253"}, - {file = "regex-2023.3.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a610e0adfcb0fc84ea25f6ea685e39e74cbcd9245a72a9a7aab85ff755a5ed27"}, - {file = "regex-2023.3.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086afe222d58b88b62847bdbd92079b4699350b4acab892f88a935db5707c790"}, - {file = "regex-2023.3.23-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79e29fd62fa2f597a6754b247356bda14b866131a22444d67f907d6d341e10f3"}, - {file = "regex-2023.3.23-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c07ce8e9eee878a48ebeb32ee661b49504b85e164b05bebf25420705709fdd31"}, - {file = "regex-2023.3.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b036f401895e854de9fefe061518e78d506d8a919cc250dc3416bca03f6f9a"}, - {file = "regex-2023.3.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78ac8dd8e18800bb1f97aad0d73f68916592dddf233b99d2b5cabc562088503a"}, - {file = "regex-2023.3.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:539dd010dc35af935b32f248099e38447bbffc10b59c2b542bceead2bed5c325"}, - {file = "regex-2023.3.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9bf4a5626f2a0ea006bf81e8963f498a57a47d58907eaa58f4b3e13be68759d8"}, - {file = "regex-2023.3.23-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf86b4328c204c3f315074a61bc1c06f8a75a8e102359f18ce99fbcbbf1951f0"}, - {file = "regex-2023.3.23-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2848bf76673c83314068241c8d5b7fa9ad9bed866c979875a0e84039349e8fa7"}, - {file = "regex-2023.3.23-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c125a02d22c555e68f7433bac8449992fa1cead525399f14e47c2d98f2f0e467"}, - {file = "regex-2023.3.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cd1671e9d5ac05ce6aa86874dd8dfa048824d1dbe73060851b310c6c1a201a96"}, - {file = "regex-2023.3.23-cp38-cp38-win32.whl", hash = "sha256:fffe57312a358be6ec6baeb43d253c36e5790e436b7bf5b7a38df360363e88e9"}, - {file = "regex-2023.3.23-cp38-cp38-win_amd64.whl", hash = "sha256:dbb3f87e15d3dd76996d604af8678316ad2d7d20faa394e92d9394dfd621fd0c"}, - {file = "regex-2023.3.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c88e8c226473b5549fe9616980ea7ca09289246cfbdf469241edf4741a620004"}, - {file = "regex-2023.3.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6560776ec19c83f3645bbc5db64a7a5816c9d8fb7ed7201c5bcd269323d88072"}, - {file = "regex-2023.3.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b1fc2632c01f42e06173d8dd9bb2e74ab9b0afa1d698058c867288d2c7a31f3"}, - {file = "regex-2023.3.23-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdf7ad455f1916b8ea5cdbc482d379f6daf93f3867b4232d14699867a5a13af7"}, - {file = "regex-2023.3.23-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fc33b27b1d800fc5b78d7f7d0f287e35079ecabe68e83d46930cf45690e1c8c"}, - {file = "regex-2023.3.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c49552dc938e3588f63f8a78c86f3c9c75301e813bca0bef13bdb4b87ccf364"}, - {file = "regex-2023.3.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e152461e9a0aedec7d37fc66ec0fa635eca984777d3d3c3e36f53bf3d3ceb16e"}, - {file = "regex-2023.3.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:db034255e72d2995cf581b14bb3fc9c00bdbe6822b49fcd4eef79e1d5f232618"}, - {file = "regex-2023.3.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:55ae114da21b7a790b90255ea52d2aa3a0d121a646deb2d3c6a3194e722fc762"}, - {file = "regex-2023.3.23-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ef3f528fe1cc3d139508fe1b22523745aa77b9d6cb5b0bf277f48788ee0b993f"}, - {file = "regex-2023.3.23-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a81c9ec59ca2303acd1ccd7b9ac409f1e478e40e96f8f79b943be476c5fdb8bb"}, - {file = "regex-2023.3.23-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cde09c4fdd070772aa2596d97e942eb775a478b32459e042e1be71b739d08b77"}, - {file = "regex-2023.3.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3cd9f5dd7b821f141d3a6ca0d5d9359b9221e4f051ca3139320adea9f1679691"}, - {file = "regex-2023.3.23-cp39-cp39-win32.whl", hash = "sha256:7304863f3a652dab5e68e6fb1725d05ebab36ec0390676d1736e0571ebb713ef"}, - {file = "regex-2023.3.23-cp39-cp39-win_amd64.whl", hash = "sha256:54c3fa855a3f7438149de3211738dd9b5f0c733f48b54ae05aa7fce83d48d858"}, - {file = "regex-2023.3.23.tar.gz", hash = "sha256:dc80df325b43ffea5cdea2e3eaa97a44f3dd298262b1c7fe9dbb2a9522b956a7"}, + {file = "regex-2023.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1fa9651141caaafa0d6048695a4a04bc4bf39c75f250a36b1a05c9588a403a9"}, + {file = "regex-2023.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbc47670e0424a566084e15af9a253b85f90fa26e60fa07e1b10c90df4c8fd07"}, + {file = "regex-2023.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7eb07d60c385aec906b82d48447907a2bbf454d0e9ead62168de111accabaf8"}, + {file = "regex-2023.5.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:376fa2ef6a02a004b6fe4ebaa5ba370e7532ec6915efd12e33aa434517f8bbee"}, + {file = "regex-2023.5.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3965a9ab13f1bf3e4af021c7dbe9678dd9f8dc5cc9097b3d3cbbf3ad00574b5d"}, + {file = "regex-2023.5.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3633a07ffeabc14f3cd531f11794bb603267d86e4109cb811a34aee020622d3f"}, + {file = "regex-2023.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9322797fddd51ec0312a8b649d9a3ebfabf4826a204ef8e1cc11801013005323"}, + {file = "regex-2023.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b48820071a49b68ca8734e8b2bd1f26632512154816b261b614e62cc724d9f8a"}, + {file = "regex-2023.5.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8418b0ee315555ca9786daab00ec8aaf47dfb2698a5be689676e83e88b949f22"}, + {file = "regex-2023.5.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2095acff95df0bf6ec3dee672a03d3d78606b4ca419d53fbd606c559cebedf45"}, + {file = "regex-2023.5.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ebf0776fdc7a5e0ac11b6db2d69ac77479411b627a96119ffa4427ba32f3bb66"}, + {file = "regex-2023.5.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ac402ac165f42f41b3aef9e8a9c6fb204dac31faad65b3b0ae6bff4bc9d0dad2"}, + {file = "regex-2023.5.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99780a0880d3dab2bb6f863492d38ab90ffdc9daf4fbefb505524f6f3a1c9dbe"}, + {file = "regex-2023.5.4-cp310-cp310-win32.whl", hash = "sha256:c455d886838dd5a248e7f06e5573275fc854febd206eb937cf632082a06a939f"}, + {file = "regex-2023.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:e87450db3c444f41e3ac6a09b7a10ddfe54fa1e98bf60ee299fe6d11097540cd"}, + {file = "regex-2023.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ef51012493837263236781ac9598f059dc4e5a4d72627bd3ac85cbd5d1b0ee1"}, + {file = "regex-2023.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5fda1fc36dd923aee070d7aab3a85b448c8b62930900c615bb67db829281103b"}, + {file = "regex-2023.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28933aa1242814ed737b569b2baf96e4d236c52be454b5dc17afd36bf893c12"}, + {file = "regex-2023.5.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d8cc797f87c07372e7d300198e1423c2b7bd35b68f375cc6700e26158940c9a"}, + {file = "regex-2023.5.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63a92f28a3f285dae06aae83227cb66cc87256db040aaf26c1c48ae5221eccde"}, + {file = "regex-2023.5.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c2ae89c92a04b057b412f88a3359e77600ce966a740e2da212667ce795e1bdc"}, + {file = "regex-2023.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d5a0edefdf800aeef6cf559af75e614fb2eb2d0388f0b132af805fdefcf8ec6"}, + {file = "regex-2023.5.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fbc5b23b569d96b8c831574c93098b68c6d7ff2509f31268c968152ca4f2ecd0"}, + {file = "regex-2023.5.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db5d5c9c7bbcf9cbc541f8adba8c92a7a7abd0de4f0343da4e96fb78ffe9d1a1"}, + {file = "regex-2023.5.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd0c918971f79bd9a883f13f91343dd2eaefbafd4344aadfe5134a65fb821c3"}, + {file = "regex-2023.5.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:861ed1249302664f96b2e968216486a02af0a143e0f3cc6fd92b78f11aa18579"}, + {file = "regex-2023.5.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f576b8dec95456ba0157943a57f5f88c076cf96cc363ef1bf5027c2976fd487a"}, + {file = "regex-2023.5.4-cp311-cp311-win32.whl", hash = "sha256:21b653c1538cbbc1c58f6d6f3ccb4a5ce56491f0ab370ec057c1c64a152eb48c"}, + {file = "regex-2023.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:336fb3f585e239362d4f26dd6f904b15d91febfc980f47ed706858f5cee20ce6"}, + {file = "regex-2023.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ccd0d7557c4e76303a6429ec9de55cd87334809cda66c0f101831e2ce9073c1"}, + {file = "regex-2023.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e20cf2575b1330687d3dd6242f82278b3bdc09a9f36cc7ac4d45b7dd63c1f5"}, + {file = "regex-2023.5.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaff21326bc5d9be0c2f400931d39274105bd5d06650f0b0215392d1b050d404"}, + {file = "regex-2023.5.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad84e1d4be3504e7dcd6370b3e847eaf05d5d35cb0818d0bd2d1a26b58c0abd2"}, + {file = "regex-2023.5.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:770f825c7751ce43aae2088fee94f2e60f95e181223642a0bb35cbaeea92001c"}, + {file = "regex-2023.5.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70bd0c121b3c4e641e5c4e633c4581059acad774a1a62bcb15fea3470c2a61cc"}, + {file = "regex-2023.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:460672c6ec94997755bd37b00302853b9d85a5a433121c198359958e8c10ced5"}, + {file = "regex-2023.5.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:953ba37dd83c424c2cf699c64a8477645fc7c7403ffd2eb1417189eddbbfb4a7"}, + {file = "regex-2023.5.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:77b3333a6cd1161b81bcf018a9bdb3cc567074a913aa69b98b9c8c79be28565c"}, + {file = "regex-2023.5.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:c1155571edd498b6274f969517db6781500fbb24fc91ff740ea5a37c4735b3ba"}, + {file = "regex-2023.5.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:1d98e4748a60c9902ad504e862756c43cc707404fc3025f82ef5bbe50bee3b9e"}, + {file = "regex-2023.5.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:12be293d718e05f7304f715980b1a25b16e34a1ff2121740592559d066f91e67"}, + {file = "regex-2023.5.4-cp36-cp36m-win32.whl", hash = "sha256:8d5bc5035989852a4ae7dacf8dc99db7c4f21c852486777a98b8efe37af4d8d7"}, + {file = "regex-2023.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:027d4962340dbd84979fd1c40bfd7ca8362030abfbbff25f1327bbf4867f047c"}, + {file = "regex-2023.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b132e4507c6404faece005329de7b2b97653ddfeeaf84f058fe820791160dcda"}, + {file = "regex-2023.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300461ed8159f61d979971ba51f1acd1e6f9907d86888e9275165e06ea90f06"}, + {file = "regex-2023.5.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a419384c6c80d58532016c3cf6a3aca009bfb7661f33e119ba7f77ec0e28222a"}, + {file = "regex-2023.5.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b365b8fa0d5fd0208db5b0e94582edb796dde07d1f99c5a9c1ff6be172c374ee"}, + {file = "regex-2023.5.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6686256d1d435ed782ff12ef11e074705911a40d3907b986e53ca9a996e88489"}, + {file = "regex-2023.5.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c10b1388106447db0cdb8e340d06fa2d49b822368a049c36928d3c24296c2e37"}, + {file = "regex-2023.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5d321dd059fd00482537aaba919e29189ea4ab6a03528881267982bb7707f610"}, + {file = "regex-2023.5.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f82d4e0725788787216c9ae53116e6e477b2d97f29ec1e5086f5afbab5716b0"}, + {file = "regex-2023.5.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:24db1c0fc20850db47977824a99fdae81d6764adaf8192dd874185d9e7166dbb"}, + {file = "regex-2023.5.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:89f54d4bbd452a5ee01dc31ec918f7b1f32483f13d3598af1acf5ea82ad82ad3"}, + {file = "regex-2023.5.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:797bab57e1317c940030f3c15d48c01e1f16d12ba0f6a807ee0bebdc1cfe3f2d"}, + {file = "regex-2023.5.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1ad00c9aae6090d052c0ee16a2737a7154031793e6c7b58a629eed8d8aa77e31"}, + {file = "regex-2023.5.4-cp37-cp37m-win32.whl", hash = "sha256:06fe9870165d4975a8a3e27a83919b9014b35dd2ee7061a5f2be8e579294cedc"}, + {file = "regex-2023.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6469c2baf450fd1e648752b113a1fc1d67dfbad359f6171be954bacf7b09d126"}, + {file = "regex-2023.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5cc67b3562aada6682ae45f2ea40819baa6bffe38155883100f8779c22c8c087"}, + {file = "regex-2023.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5a98814d42282153c30d674ee34ea114a03ea8d32fd5d9b924d46fbeb2c7eb15"}, + {file = "regex-2023.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c704e7062c59d2f7e2eebda2c0c0b69bd807ca6579c3a21fc4b1d8505cfc090"}, + {file = "regex-2023.5.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2bc7249840faacfff6196e5b5ffeb3fdaf078986521a1cda34e9be5607e773b"}, + {file = "regex-2023.5.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0ac14c36d91b191d1cb073fb5ac49937d88a5c8b051ced3875321a525202c34"}, + {file = "regex-2023.5.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2d66c1fd898d81b8ce0f95afab9ba0ab522cf08810f11fa28ad958706cd2b2"}, + {file = "regex-2023.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0130a2fdd6f033c3e3710d0b950cc6abd3133c5af88c40c78e77641cb6f6cf8a"}, + {file = "regex-2023.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff8fef88029d0420315935041db517855ea022889fa8d54959943e39fffebf59"}, + {file = "regex-2023.5.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2762750332f57820c0f38ade87ce4ebe671b178892faed0112f574f0b42801cf"}, + {file = "regex-2023.5.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dbcb49036a6a6065035ac2acc1ad6a918f9e09ef2d0f9392dc90b8756f789f95"}, + {file = "regex-2023.5.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:c40f7e8c02b287550166a3e36dbb89f9387db86a71101f6242668e3ef979cd2a"}, + {file = "regex-2023.5.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1dc5e9a847613679ac8bd0386a0e54f2958441a0fcc123778637e433041aa763"}, + {file = "regex-2023.5.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa92f9ad481108a7e4c5a5234608f7c718f8b67003ce4719a4d2735d82b54167"}, + {file = "regex-2023.5.4-cp38-cp38-win32.whl", hash = "sha256:00f6f26e748c797a041ab6957f4cacc66a7fbd5dc5f627760985f5c5b7de2af6"}, + {file = "regex-2023.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:6f02105d4a511f550dcd63f750937d1607a1f6dc253c798c4adf36aba89215a3"}, + {file = "regex-2023.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49a77f0b62a4122cf578d1194658973c435e6d2a9611013be11b6750056a5930"}, + {file = "regex-2023.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a664857dd9b1076942c4d73c54a031066ee0ae88a438e7a1e0e79c1c5ddf47a"}, + {file = "regex-2023.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b887d87188489859411d0c7e741f7dfe8a3ca5946b0db8b3c9e5daecc089b62"}, + {file = "regex-2023.5.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037f4be6a240a11a6d3397e932ef5d3ec5855858910792a0ab7d351bd0333533"}, + {file = "regex-2023.5.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bda905e040e6c2875a7dde9652a9e0c426aaac6058568cc064f8128b061439ee"}, + {file = "regex-2023.5.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91f47522688955cb33190f8354ccaa1cc058d05e73f99afe9ace40db36c159e8"}, + {file = "regex-2023.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a7ab3440f0c653dee8b42af858da6e07615c64ba86a6b3509a0ecf44eabdb11"}, + {file = "regex-2023.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04a825fd9f5931263eccd0cbdbf171a9792fa1bf2642ca62800b57689ca1b660"}, + {file = "regex-2023.5.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0eee66c4ce6ced2e9d5d4497a569bab6257a6d118eb43dd57cceb61ba00b62d8"}, + {file = "regex-2023.5.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e708e69c4d3bc41df29efb94aadc5578c841b2cd02f8cbb1bcfbf280f2801238"}, + {file = "regex-2023.5.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:b87d38717ed855583ae1693f6095fc9c06b7dde4ddec782b41aa92931dd60e7c"}, + {file = "regex-2023.5.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:50e00ab84396bfbeb1bede61eff6641f957b6532e74e02be480d71914e20e2ac"}, + {file = "regex-2023.5.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0c731522eaf74166066fcf91fe7fe3c3617cc5e8df0c150132282d0dd5225afc"}, + {file = "regex-2023.5.4-cp39-cp39-win32.whl", hash = "sha256:97fd2885df308edcdf96baa632192a4291f3ed5b072c0bc3f29dc1e6de40ffa4"}, + {file = "regex-2023.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:cb22580ce5e2eee138a78df40444151ff51c91acd11be546216a046677c75593"}, + {file = "regex-2023.5.4.tar.gz", hash = "sha256:9e1b4b0b4baff934ef3c0ac56578a6b773f7f90ad1db3ff843ee40d83bdae09f"}, ] [[package]] name = "requests" -version = "2.28.2" +version = "2.29.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" files = [ - {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, - {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, + {file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"}, + {file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"}, ] [package.dependencies] @@ -3552,6 +4099,109 @@ files = [ {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, ] +[[package]] +name = "scikit-image" +version = "0.19.3" +description = "Image processing in Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "scikit-image-0.19.3.tar.gz", hash = "sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450"}, + {file = "scikit_image-0.19.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8"}, + {file = "scikit_image-0.19.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc"}, + {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19"}, + {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7"}, + {file = "scikit_image-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9"}, + {file = "scikit_image-0.19.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2"}, + {file = "scikit_image-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732"}, + {file = "scikit_image-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe"}, + {file = "scikit_image-0.19.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef"}, + {file = "scikit_image-0.19.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b"}, + {file = "scikit_image-0.19.3-cp38-cp38-win32.whl", hash = "sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34"}, + {file = "scikit_image-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d"}, + {file = "scikit_image-0.19.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced"}, + {file = "scikit_image-0.19.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5"}, + {file = "scikit_image-0.19.3-cp39-cp39-win32.whl", hash = "sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e"}, + {file = "scikit_image-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919"}, +] + +[package.dependencies] +imageio = ">=2.4.1" +networkx = ">=2.2" +numpy = ">=1.17.0" +packaging = ">=20.0" +pillow = ">=6.1.0,<7.1.0 || >7.1.0,<7.1.1 || >7.1.1,<8.3.0 || >8.3.0" +PyWavelets = ">=1.1.1" +scipy = ">=1.4.1" +tifffile = ">=2019.7.26" + +[package.extras] +data = ["pooch (>=1.3.0)"] +docs = ["cloudpickle (>=0.2.1)", "dask[array] (>=0.15.0,!=2.17.0)", "ipywidgets", "kaleido", "matplotlib (>=3.3)", "myst-parser", "numpydoc (>=1.0)", "pandas (>=0.23.0)", "plotly (>=4.14.0)", "pooch (>=1.3.0)", "pytest-runner", "scikit-learn", "seaborn (>=0.7.1)", "sphinx (>=1.8)", "sphinx-copybutton", "sphinx-gallery (>=0.10.1)", "tifffile (>=2020.5.30)"] +optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pyamg", "qtpy"] +test = ["asv", "codecov", "flake8", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"] + +[[package]] +name = "scikit-image" +version = "0.20.0" +description = "Image processing in Python" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "scikit_image-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3cec8c5e8412ee19642a916648144186eb6b60c39fb6608ab478b4d1a4575e25"}, + {file = "scikit_image-0.20.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0ab378822fadc93db7e917a266d489ea33df3b42edfef197caaebbabbc2e4ecc"}, + {file = "scikit_image-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6797e3ef5fc53897bde131cfc3ceba6ce247d89cfe194fc8d3aba7f5c12aaf6"}, + {file = "scikit_image-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f667dcf01737248bc5bd0a99fad58475abeb6b6a8229aecee9fdb96cf988ae85"}, + {file = "scikit_image-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:79a400ffe35fc7f64d1d043f3d043e062015689ad5637c35cd5569edae87ae13"}, + {file = "scikit_image-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:049d955869620453b9e0568c2da62c8fec47bf3714be48b5d46bbaebb91bdc1f"}, + {file = "scikit_image-0.20.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:a503ee85b444234ee88f34bf8674872dc37c6124ff60b7eb9242813de012ff4e"}, + {file = "scikit_image-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3943d7355d02b40c066fd87cd5fe1b4f6637a16448e62333c4191a65ebf40a1c"}, + {file = "scikit_image-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d719242ea7e7250d49e38d1e33c44c2dd59c3414ae085881d168b98cbb6059a"}, + {file = "scikit_image-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:fdd1fd258e78c86e382fd687177431088a40880bd785e0ab40ee5f3794366710"}, + {file = "scikit_image-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1cd0486cb769d906307a3ec3884630be822d8ec2f41069e197336f904f584a33"}, + {file = "scikit_image-0.20.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:2e9026161d0a698f532352dda6455a0bc13b1c9d831ea9279726b59d064df574"}, + {file = "scikit_image-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c123e6b0677dc1697c04b5bf2efb7110bcca511b4bc6967a38fa395ae5edf44"}, + {file = "scikit_image-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76f2fd12b537daea806a078df9ea76f5cc5a529d5bd7c41d7d0a101e9c5f91c4"}, + {file = "scikit_image-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:2118d610096754bca44b5d37328e1382e5fa7c6493803685100c9238e257d848"}, + {file = "scikit_image-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:13a5c1c81ee5bcb64ee8ca8f1a2cf371b0c4345ea6fb67c3052e1c6d5edbd936"}, + {file = "scikit_image-0.20.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:1794889d2dbb385c7ad5656363371ba0057b7a3335cda093a11415af84bb96e2"}, + {file = "scikit_image-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df14f8a55dae511749b081d9402ea215ea7c641bd6f74f06aa7b623e132817df"}, + {file = "scikit_image-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b856efc75e3051bea6d40a8ffcdaabd5682783ece1aa91c3f6777c3372a98ca1"}, + {file = "scikit_image-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:a600374394b76b7fc260cef54e1be21047c4de0ecffb0b7f2f7392cd8ba16ffa"}, + {file = "scikit_image-0.20.0.tar.gz", hash = "sha256:2cd784fce18bd31d71ade62c6221440199ead03acf7544086261ee032264cf61"}, +] + +[package.dependencies] +imageio = ">=2.4.1" +lazy_loader = ">=0.1" +networkx = ">=2.8" +numpy = ">=1.21.1" +packaging = ">=20.0" +pillow = ">=9.0.1" +PyWavelets = ">=1.1.1" +scipy = {version = ">=1.8", markers = "python_version > \"3.9\""} +tifffile = ">=2019.7.26" + +[package.extras] +build = ["Cython (>=0.29.24)", "build", "meson-python (>=0.13.0rc0)", "ninja", "numpy (>=1.21.1)", "packaging (>=20)", "pythran", "setuptools (>=67)", "wheel"] +data = ["pooch (>=1.3.0)"] +default = ["PyWavelets (>=1.1.1)", "imageio (>=2.4.1)", "lazy_loader (>=0.1)", "networkx (>=2.8)", "numpy (>=1.21.1)", "packaging (>=20.0)", "pillow (>=9.0.1)", "scipy (>=1.8)", "scipy (>=1.8,<1.9.2)", "tifffile (>=2019.7.26)"] +developer = ["pre-commit", "rtoml"] +docs = ["dask[array] (>=2022.9.2)", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.5)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pytest-runner", "scikit-learn", "seaborn (>=0.11)", "sphinx (>=5.2)", "sphinx-copybutton", "sphinx-gallery (>=0.11)", "tifffile (>=2022.8.12)"] +optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.3)", "pooch (>=1.3.0)", "pyamg"] +test = ["asv", "codecov", "matplotlib (>=3.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"] + [[package]] name = "scipy" version = "1.10.1" @@ -3593,14 +4243,14 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo [[package]] name = "send2trash" -version = "1.8.0" -description = "Send file to trash natively under Mac OS X, Windows and Linux." +version = "1.8.2" +description = "Send file to trash natively under Mac OS X, Windows and Linux" category = "main" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ - {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, - {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, + {file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"}, + {file = "Send2Trash-1.8.2.tar.gz", hash = "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"}, ] [package.extras] @@ -3608,6 +4258,23 @@ nativelib = ["pyobjc-framework-Cocoa", "pywin32"] objc = ["pyobjc-framework-Cocoa"] win32 = ["pywin32"] +[[package]] +name = "setuptools" +version = "67.7.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, + {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -3632,6 +4299,18 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -3656,6 +4335,172 @@ files = [ {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] +[[package]] +name = "sphinx" +version = "6.2.1" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, + {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.13" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.2.0" +description = "Read the Docs theme for Sphinx" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "sphinx_rtd_theme-1.2.0-py2.py3-none-any.whl", hash = "sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2"}, + {file = "sphinx_rtd_theme-1.2.0.tar.gz", hash = "sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8"}, +] + +[package.dependencies] +docutils = "<0.19" +sphinx = ">=1.6,<7" +sphinxcontrib-jquery = {version = ">=2.0.0,<3.0.0 || >3.0.0", markers = "python_version > \"3\""} + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +category = "dev" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + [[package]] name = "stack-data" version = "0.6.2" @@ -3678,14 +4523,14 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "sympy" -version = "1.10.1" +version = "1.11.1" description = "Computer algebra system (CAS) in Python" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "sympy-1.10.1-py3-none-any.whl", hash = "sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130"}, - {file = "sympy-1.10.1.tar.gz", hash = "sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b"}, + {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"}, + {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"}, ] [package.dependencies] @@ -3703,6 +4548,21 @@ files = [ {file = "tblib-1.7.0.tar.gz", hash = "sha256:059bd77306ea7b419d4f76016aef6d7027cc8a0785579b5aad198803435f882c"}, ] +[[package]] +name = "tenacity" +version = "8.2.2" +description = "Retry code until it succeeds" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"}, + {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"}, +] + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + [[package]] name = "terminado" version = "0.17.1" @@ -3724,6 +4584,24 @@ tornado = ">=6.1.0" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] +[[package]] +name = "tifffile" +version = "2023.4.12" +description = "Read and write TIFF files" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tifffile-2023.4.12-py3-none-any.whl", hash = "sha256:3161954746fe32c4f4244d0fb2eb0a272f3a3760b78882a42faa83ac5e6e0b74"}, + {file = "tifffile-2023.4.12.tar.gz", hash = "sha256:2fa99f9890caab919d932a0acaa9d0f5843dc2ef3594e212963932e20713badd"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +all = ["defusedxml", "fsspec", "imagecodecs (>=2023.1.23)", "lxml", "matplotlib", "zarr"] + [[package]] name = "tinycss2" version = "1.2.1" @@ -3769,23 +4647,23 @@ files = [ [[package]] name = "tornado" -version = "6.3" +version = "6.3.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." category = "main" optional = false python-versions = ">= 3.8" files = [ - {file = "tornado-6.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:6cfff1e9c15c79e106b8352269d201f8fc0815914a6260f3893ca18b724ea94b"}, - {file = "tornado-6.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6164571f5b9f73143d1334df4584cb9ac86d20c461e17b6c189a19ead8bb93c1"}, - {file = "tornado-6.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4546003dc8b5733489139d3bff5fa6a0211be505faf819bd9970e7c2b32e8122"}, - {file = "tornado-6.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c659ab04d5aa477dbe44152c67d93f3ad3243b992d94f795ca1d5c73c37337ce"}, - {file = "tornado-6.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912df5712024564e362ecce43c8d5862e14c78c8dd3846c9d889d44fbd7f4951"}, - {file = "tornado-6.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c37b6a384d54ce6a31168d40ab21ad2591ddaf34973075cc0cad154402ecd9e8"}, - {file = "tornado-6.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:c9114a61a4588c09065b9996ae05462350d17160b92b9bf9a1e93689cc0424dc"}, - {file = "tornado-6.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:4d349846931557b7ec92f224b5d598b160e2ba26ae1812480b42e9622c884bf7"}, - {file = "tornado-6.3-cp38-abi3-win32.whl", hash = "sha256:d7b737e18f701de3e4a3b0824260b4d740e4d60607b8089bb80e80ffd464780e"}, - {file = "tornado-6.3-cp38-abi3-win_amd64.whl", hash = "sha256:720f53e6367b38190ae7fa398c25c086c69d88b3c6535bd6021a126b727fb5cd"}, - {file = "tornado-6.3.tar.gz", hash = "sha256:d68f3192936ff2c4add04dc21a436a43b4408d466746b78bb2b9d0a53a18683f"}, + {file = "tornado-6.3.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:db181eb3df8738613ff0a26f49e1b394aade05034b01200a63e9662f347d4415"}, + {file = "tornado-6.3.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b4e7b956f9b5e6f9feb643ea04f07e7c6b49301e03e0023eedb01fa8cf52f579"}, + {file = "tornado-6.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9661aa8bc0e9d83d757cd95b6f6d1ece8ca9fd1ccdd34db2de381e25bf818233"}, + {file = "tornado-6.3.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81c17e0cc396908a5e25dc8e9c5e4936e6dfd544c9290be48bd054c79bcad51e"}, + {file = "tornado-6.3.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a27a1cfa9997923f80bdd962b3aab048ac486ad8cfb2f237964f8ab7f7eb824b"}, + {file = "tornado-6.3.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d7117f3c7ba5d05813b17a1f04efc8e108a1b811ccfddd9134cc68553c414864"}, + {file = "tornado-6.3.1-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:ffdce65a281fd708da5a9def3bfb8f364766847fa7ed806821a69094c9629e8a"}, + {file = "tornado-6.3.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:90f569a35a8ec19bde53aa596952071f445da678ec8596af763b9b9ce07605e6"}, + {file = "tornado-6.3.1-cp38-abi3-win32.whl", hash = "sha256:3455133b9ff262fd0a75630af0a8ee13564f25fb4fd3d9ce239b8a7d3d027bf8"}, + {file = "tornado-6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:1285f0691143f7ab97150831455d4db17a267b59649f7bd9700282cba3d5e771"}, + {file = "tornado-6.3.1.tar.gz", hash = "sha256:5e2f49ad371595957c50e42dd7e5c14d64a6843a3cf27352b69c706d1b5918af"}, ] [[package]] @@ -3869,6 +4747,72 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uvloop" +version = "0.17.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, +] + +[package.extras] +dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "virtualenv" +version = "20.23.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"}, + {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.11,<4" +platformdirs = ">=3.2,<4" + +[package.extras] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] + [[package]] name = "watchdog" version = "3.0.0" @@ -3966,6 +4910,24 @@ docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] +[[package]] +name = "werkzeug" +version = "2.3.3" +description = "The comprehensive WSGI web application library." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.3-py3-none-any.whl", hash = "sha256:4866679a0722de00796a74086238bb3b98d90f423f05de039abb09315487254a"}, + {file = "Werkzeug-2.3.3.tar.gz", hash = "sha256:a987caf1092edc7523edb139edb20c70571c4a8d5eed02e0b547b4739174d091"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + [[package]] name = "y-py" version = "0.5.9" @@ -4093,4 +5055,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "559de371f791faaaa342c033d4aa45005e679af05d43531eccc0cc03d4cf8e06" +content-hash = "f41458cb9df30c2d794654d124813d617ad360116bdb31437cf8a357069afe9c" diff --git a/pyproject.toml b/pyproject.toml index 73fde1b6..2f302f68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,35 @@ hdf5storage = "^0.1.18" psutil = "^5.9.0" nest-asyncio = "^1.5.6" +[tool.poetry.group.stride.dependencies] +blosc = "*" +cached-property = "*" +click = "*" +cloudpickle = ">=1.6" +cython = "*" +dash = ">=2.0.0" +flake8 = "*" +gputil = "*" +h5py = ">=3.2" +multiprocess = "*" +nbval = "*" +numpy = ">=1.20" +pre_commit = "*" +psutil = "*" +py-libnuma = "*" +pyflakes = "*" +pytest = "*" +pytest-cov = "*" +pytest-runner = "*" +python-daemon = "*" +pyyaml = ">=5.4" +pyzmq = ">=22.1" +scikit-image = "*" +scipy = ">=1.6" +sphinx = "*" +sphinx_rtd_theme = "*" +uvloop = "*" +zict = "*" [tool.poetry.group.dev.dependencies] black = "^22.8.0" From ce015abc8f70d7401c61cf2bc14b480cd3b8a2b9 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 2 May 2023 19:06:40 -0300 Subject: [PATCH 007/198] Updating docs and CI to install stride with no dependencies --- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- Dockerfile | 2 +- README.md | 2 +- docs/contributing.md | 2 +- docs/index.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 62193354..8f9334c6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,6 +40,6 @@ jobs: run: poetry install --no-interaction - name: Install stride - run: poetry run pip install git+https://github.com/trustimaging/stride + run: poetry run pip install git+https://github.com/trustimaging/stride --no-deps - run: poetry run mkdocs gh-deploy --force diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1689bc4c..9c44171f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,7 +39,7 @@ jobs: run: poetry install --no-interaction - name: Install stride - run: poetry run pip install git+https://github.com/trustimaging/stride + run: poetry run pip install git+https://github.com/trustimaging/stride --no-deps - name: Run linting run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85501640..e60447a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,7 @@ jobs: run: poetry install --no-interaction - name: Install stride - run: poetry run pip install git+https://github.com/trustimaging/stride + run: poetry run pip install git+https://github.com/trustimaging/stride --no-deps - name: Run tests run: | diff --git a/Dockerfile b/Dockerfile index aef4ceee..9016db34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ENV POETRY_VIRTUALENVS_CREATE=false COPY . ./ RUN /venv/bin/poetry install && \ - /venv/bin/pip install git+https://github.com/trustimaging/stride + /venv/bin/pip install git+https://github.com/trustimaging/stride --no-deps FROM python:3.10.0-slim diff --git a/README.md b/README.md index c986dcc0..1aec3ed6 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ pip install neurotechdevkit And then you must install stride using: ``` bash -pip install git+https://github.com/trustimaging/stride +pip install git+https://github.com/trustimaging/stride --no-deps ``` ### Development diff --git a/docs/contributing.md b/docs/contributing.md index cbf336d9..47081707 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -59,7 +59,7 @@ This will resolve and install the dependencies from `poetry.lock` and will insta Install stride with ```bash -$ poetry run pip install git+https://github.com/trustimaging/stride +$ poetry run pip install git+https://github.com/trustimaging/stride --no-deps ``` ### Using the environment diff --git a/docs/index.md b/docs/index.md index ad675a33..9b6ac6a4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ pip install neurotechdevkit And then you must install stride using: ``` bash -pip install git+https://github.com/trustimaging/stride +pip install git+https://github.com/trustimaging/stride --no-deps ``` ### Docker From 303f916e2c9a71f3435a4a28b1c9884ae553dffe Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Tue, 2 May 2023 22:13:25 +0000 Subject: [PATCH 008/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2f302f68..e0900ccc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.9" +version = "v0.0.10" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 2a6596f7858acd5377c8f595c0def3b1b67432fc Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 3 May 2023 07:43:07 -0300 Subject: [PATCH 009/198] Changing scipy dependency of ndk so that it doesn't conflict with stride's dependency --- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- Dockerfile | 2 +- README.md | 2 +- docs/contributing.md | 2 +- docs/index.md | 2 +- poetry.lock | 933 +------------------------------------ pyproject.toml | 32 +- 9 files changed, 9 insertions(+), 970 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8f9334c6..62193354 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,6 +40,6 @@ jobs: run: poetry install --no-interaction - name: Install stride - run: poetry run pip install git+https://github.com/trustimaging/stride --no-deps + run: poetry run pip install git+https://github.com/trustimaging/stride - run: poetry run mkdocs gh-deploy --force diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9c44171f..1689bc4c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,7 +39,7 @@ jobs: run: poetry install --no-interaction - name: Install stride - run: poetry run pip install git+https://github.com/trustimaging/stride --no-deps + run: poetry run pip install git+https://github.com/trustimaging/stride - name: Run linting run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e60447a4..85501640 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,7 @@ jobs: run: poetry install --no-interaction - name: Install stride - run: poetry run pip install git+https://github.com/trustimaging/stride --no-deps + run: poetry run pip install git+https://github.com/trustimaging/stride - name: Run tests run: | diff --git a/Dockerfile b/Dockerfile index 9016db34..aef4ceee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ENV POETRY_VIRTUALENVS_CREATE=false COPY . ./ RUN /venv/bin/poetry install && \ - /venv/bin/pip install git+https://github.com/trustimaging/stride --no-deps + /venv/bin/pip install git+https://github.com/trustimaging/stride FROM python:3.10.0-slim diff --git a/README.md b/README.md index 1aec3ed6..c986dcc0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ pip install neurotechdevkit And then you must install stride using: ``` bash -pip install git+https://github.com/trustimaging/stride --no-deps +pip install git+https://github.com/trustimaging/stride ``` ### Development diff --git a/docs/contributing.md b/docs/contributing.md index 47081707..cbf336d9 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -59,7 +59,7 @@ This will resolve and install the dependencies from `poetry.lock` and will insta Install stride with ```bash -$ poetry run pip install git+https://github.com/trustimaging/stride --no-deps +$ poetry run pip install git+https://github.com/trustimaging/stride ``` ### Using the environment diff --git a/docs/index.md b/docs/index.md index 9b6ac6a4..ad675a33 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ pip install neurotechdevkit And then you must install stride using: ``` bash -pip install git+https://github.com/trustimaging/stride --no-deps +pip install git+https://github.com/trustimaging/stride ``` ### Docker diff --git a/poetry.lock b/poetry.lock index 667d6970..ca10872c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,18 +28,6 @@ files = [ dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"] -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] - [[package]] name = "anyio" version = "3.6.2" @@ -312,61 +300,6 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] -[[package]] -name = "blinker" -version = "1.6.2" -description = "Fast, simple object-to-object and broadcast signaling" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, - {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, -] - -[[package]] -name = "blosc" -version = "1.11.1" -description = "Blosc data compressor" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "blosc-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:345577759e5e1cd0905faf89ad1dbcc60bb7a56b759985a417c5b0cab0e0117a"}, - {file = "blosc-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8907404c3058ac4a767a0a8b279c4c5aaf90431b7238f5b9b464eebe50b91010"}, - {file = "blosc-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41578759a913122ba63c24750cb53f861a557a0946f5bb80ad62625e95c7f45a"}, - {file = "blosc-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bba4cfbc0a86788eb38acc05eb35ba97e2897441416c5f927f243ed7b4b406b6"}, - {file = "blosc-1.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:705df0302ba73293b6109800110994841d5de4491318a3b06c5bbcec32756939"}, - {file = "blosc-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bef546a039a1d984c6d2ed516ffcbc2a09dbbba61a0951c5197fad3f718095b1"}, - {file = "blosc-1.11.1-cp310-cp310-win32.whl", hash = "sha256:5d0ceff7a19f3c5c42d9d5ec13a90ae0b69cb2731e797a58ca0e3b25018cbb69"}, - {file = "blosc-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:f889715e28bbdc2f927a546f8debf403947cea1e3dd8f15e1428b66701416ee6"}, - {file = "blosc-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88c2eba2c9c013e44d5841660f7832425ebdc0bcfce9e11c59a8eb732f28cf9d"}, - {file = "blosc-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8321241c37f25ad4a21e3e0731246753b557dd8910eaee32bf7510eeee03648a"}, - {file = "blosc-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:417fb44fd50887e4b3e2f8fc91f350c791bf0e39a1b4d45a41071fc416fb767a"}, - {file = "blosc-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74c7bc804c608de4dc53c873f8ef5b481b9e3b0b6381e7d3fcff92683de9547"}, - {file = "blosc-1.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:94010a4fc094dda5a9abd9546e1f97af9a7dbd903a6eee39dd13244c7c419412"}, - {file = "blosc-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dfbb2bb0a3a69006ad0c627247b53e8ed5f844fc5cd5f09979f1e174f6dc4a6e"}, - {file = "blosc-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7b40a99add3120a26bc92e7a751a01e4aa854fc1835e0d12e9cf77dbf5498a2a"}, - {file = "blosc-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:751a4ad8b4696d1c54f05a5255fe911d72f424cd03a9ce97525d67f40946b2ea"}, - {file = "blosc-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf25b0aaf209411ad7ff65f42ee6c42e87d2a9161990743cb21ea264a4509e7b"}, - {file = "blosc-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2078f8d4500c83387a0e1c3f750c9a6d4848a80e1bf07ec1fd9c87bb1e2e2e92"}, - {file = "blosc-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:038ab5690c016a15cc5bf47977ae57b9cb4517f5df3eccd895c2d6c6656da6e6"}, - {file = "blosc-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e2283eb29c5938bfc63ae4091e0b3df5ef0492f83b9ffffa184532d1e5e7f8c"}, - {file = "blosc-1.11.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1bfc0a2cd115aa6eac75b841840b00fee0dd7bf77793650bdf470e12cf105627"}, - {file = "blosc-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:31ed162c9cc916c393eb9dada1beb632b11d4646afdf708c1c3f9d38402a4fab"}, - {file = "blosc-1.11.1-cp38-cp38-win32.whl", hash = "sha256:b113c900cfb47272b63f0dbee95016e24720d24d46009aa558e131128fb26aa5"}, - {file = "blosc-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:da627a51bc2947a698097ac0ccf2f6e51a3aed77f2d4297295365ef8a18d748e"}, - {file = "blosc-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:69cf3d2bc196befe4adddc3cca08315fd9e05208887b9fe031942e204d48ed49"}, - {file = "blosc-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:270cf6aa4da391950e0140cc9ee9ced8562e04d4eb1473fa42fc454b81eaeb80"}, - {file = "blosc-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24dfeafbbe0b4b6a028fcfd22c3920be738d6ef9e9d2ffcb2b66153f18ad455d"}, - {file = "blosc-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf07fccaa1d76fdb03d4eed641a71f2f54be1d1bffba9300bab4c8f437017b46"}, - {file = "blosc-1.11.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c793b3e33d025161bd3f8dcacc9bdfa011218eafbc607e195360456d3306ebf9"}, - {file = "blosc-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa129006fabeec63a1e54d30863e0a0330ec72caedbd5a5c282121796e84e1a0"}, - {file = "blosc-1.11.1-cp39-cp39-win32.whl", hash = "sha256:213ce859c89625c64776c8a59c6b235bca6dea81c185a273703366e733880ff5"}, - {file = "blosc-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c6371779ca9ec469735b05ee0a119cc431d72008f9f2813ca73d4acf2b39b2f"}, - {file = "blosc-1.11.1.tar.gz", hash = "sha256:c22119b27bae1063a697f639028b422d55811b0880c3fc0149cbdea791d0b276"}, -] - [[package]] name = "cached-property" version = "1.5.2" @@ -468,18 +401,6 @@ files = [ [package.dependencies] pycparser = "*" -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] - [[package]] name = "cgen" version = "2020.1" @@ -822,119 +743,6 @@ files = [ {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, ] -[[package]] -name = "cython" -version = "0.29.34" -description = "The Cython compiler for writing C extensions for the Python language." -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "Cython-0.29.34-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:742544024ddb74314e2d597accdb747ed76bd126e61fcf49940a5b5be0a8f381"}, - {file = "Cython-0.29.34-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03daae07f8cbf797506446adae512c3dd86e7f27a62a541fa1ee254baf43e32c"}, - {file = "Cython-0.29.34-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a8de3e793a576e40ca9b4f5518610cd416273c7dc5e254115656b6e4ec70663"}, - {file = "Cython-0.29.34-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60969d38e6a456a67e7ef8ae20668eff54e32ba439d4068ccf2854a44275a30f"}, - {file = "Cython-0.29.34-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:21b88200620d80cfe193d199b259cdad2b9af56f916f0f7f474b5a3631ca0caa"}, - {file = "Cython-0.29.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:308c8f1e58bf5e6e8a1c4dcf8abbd2d13d0f9b1e582f4d9ae8b89857342d8bb5"}, - {file = "Cython-0.29.34-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d8f822fb6ecd5d88c42136561f82960612421154fc5bf23c57103a367bb91356"}, - {file = "Cython-0.29.34-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56866323f1660cecb4d5ff3a1fba92a56b91b7cfae0a8253777aa4bdb3bdf9a8"}, - {file = "Cython-0.29.34-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e971db8aeb12e7c0697cefafe65eefcc33ff1224ae3d8c7f83346cbc42c6c270"}, - {file = "Cython-0.29.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4401270b0dc464c23671e2e9d52a60985f988318febaf51b047190e855bbe7d"}, - {file = "Cython-0.29.34-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:dce0a36d163c05ae8b21200059511217d79b47baf2b7b0f926e8367bd7a3cc24"}, - {file = "Cython-0.29.34-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dbd79221869ee9a6ccc4953b2c8838bb6ae08ab4d50ea4b60d7894f03739417b"}, - {file = "Cython-0.29.34-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a0f4229df10bc4545ebbeaaf96ebb706011d8b333e54ed202beb03f2bee0a50e"}, - {file = "Cython-0.29.34-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd1ea21f1cebf33ae288caa0f3e9b5563a709f4df8925d53bad99be693fc0d9b"}, - {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d7ef5f68f4c5baa93349ea54a352f8716d18bee9a37f3e93eff38a5d4e9b7262"}, - {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:459994d1de0f99bb18fad9f2325f760c4b392b1324aef37bcc1cd94922dfce41"}, - {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1d6c809e2f9ce5950bbc52a1d2352ef3d4fc56186b64cb0d50c8c5a3c1d17661"}, - {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f674ceb5f722d364395f180fbac273072fc1a266aab924acc9cfd5afc645aae1"}, - {file = "Cython-0.29.34-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9489de5b2044dcdfd9d6ca8242a02d560137b3c41b1f5ae1c4f6707d66d6e44d"}, - {file = "Cython-0.29.34-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5c121dc185040f4333bfded68963b4529698e1b6d994da56be32c97a90c896b6"}, - {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b6149f7cc5b31bccb158c5b968e5a8d374fdc629792e7b928a9b66e08b03fca5"}, - {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0ab3cbf3d62b0354631a45dc93cfcdf79098663b1c65a6033af4a452b52217a7"}, - {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:4a2723447d1334484681d5aede34184f2da66317891f94b80e693a2f96a8f1a7"}, - {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e40cf86aadc29ecd1cb6de67b0d9488705865deea4fc185c7ad56d7a6fc78703"}, - {file = "Cython-0.29.34-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8c3cd8bb8e880a3346f5685601004d96e0a2221e73edcaeea57ea848618b4ac6"}, - {file = "Cython-0.29.34-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0e9032cd650b0cb1d2c2ef2623f5714c14d14c28d7647d589c3eeed0baf7428e"}, - {file = "Cython-0.29.34-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bdb3285660e3068438791ace7dd7b1efd6b442a10b5c8d7a4f0c9d184d08c8ed"}, - {file = "Cython-0.29.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a8ad755f9364e720f10a36734a1c7a5ced5c679446718b589259261438a517c9"}, - {file = "Cython-0.29.34-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7595d29eaee95633dd8060f50f0e54b27472d01587659557ebcfe39da3ea946b"}, - {file = "Cython-0.29.34-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ef7879668214d80ea3914c17e7d4e1ebf4242e0dd4dabe95ca5ccbe75589a5"}, - {file = "Cython-0.29.34-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ccb223b5f0fd95d8d27561efc0c14502c0945f1a32274835831efa5d5baddfc1"}, - {file = "Cython-0.29.34-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:11b1b278b8edef215caaa5250ad65a10023bfa0b5a93c776552248fc6f60098d"}, - {file = "Cython-0.29.34-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5718319a01489688fdd22ddebb8e2fcbbd60be5f30de4336ea7063c3ae29fbe5"}, - {file = "Cython-0.29.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:cfb2302ef617d647ee590a4c0a00ba3c2da05f301dcefe7721125565d2e51351"}, - {file = "Cython-0.29.34-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:67b850cf46b861bc27226d31e1d87c0e69869a02f8d3cc5d5bef549764029879"}, - {file = "Cython-0.29.34-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0963266dad685812c1dbb758fcd4de78290e3adc7db271c8664dcde27380b13e"}, - {file = "Cython-0.29.34-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7879992487d9060a61393eeefe00d299210256928dce44d887b6be313d342bac"}, - {file = "Cython-0.29.34-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:44733366f1604b0c327613b6918469284878d2f5084297d10d26072fc6948d51"}, - {file = "Cython-0.29.34-py2.py3-none-any.whl", hash = "sha256:be4f6b7be75a201c290c8611c0978549c60353890204573078e865423dbe3c83"}, - {file = "Cython-0.29.34.tar.gz", hash = "sha256:1909688f5d7b521a60c396d20bba9e47a1b2d2784bfb085401e1e1e7d29a29a8"}, -] - -[[package]] -name = "dash" -version = "2.9.3" -description = "A Python framework for building reactive web-apps. Developed by Plotly." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "dash-2.9.3-py3-none-any.whl", hash = "sha256:a749ae1ea9de3fe7b785353a818ec9b629d39c6b7e02462954203bd1e296fd0e"}, - {file = "dash-2.9.3.tar.gz", hash = "sha256:47392f8d6455dc989a697407eb5941f3bad80604df985ab1ac9d4244568ffb34"}, -] - -[package.dependencies] -dash-core-components = "2.0.0" -dash-html-components = "2.0.0" -dash-table = "5.0.0" -Flask = ">=1.0.4" -plotly = ">=5.0.0" - -[package.extras] -celery = ["celery[redis] (>=5.1.2)", "importlib-metadata (<5)", "redis (>=3.5.3)"] -ci = ["black (==21.6b0)", "black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==3.9.2)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "isort (==4.3.21)", "mimesis", "mock (==4.0.3)", "numpy", "openpyxl", "orjson (==3.5.4)", "orjson (==3.6.7)", "pandas (==1.1.5)", "pandas (>=1.4.0)", "preconditions", "pyarrow", "pyarrow (<3)", "pylint (==2.13.5)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "xlrd (<2)", "xlrd (>=2.0.1)"] -compress = ["flask-compress"] -dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"] -diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] -testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4)", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"] - -[[package]] -name = "dash-core-components" -version = "2.0.0" -description = "Core component suite for Dash" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, - {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, -] - -[[package]] -name = "dash-html-components" -version = "2.0.0" -description = "Vanilla HTML components for Dash" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, - {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, -] - -[[package]] -name = "dash-table" -version = "5.0.0" -description = "Dash table" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, - {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, -] - [[package]] name = "dask" version = "2023.3.2" @@ -1055,33 +863,6 @@ extras = ["matplotlib", "pandas"] mpi = ["ipyparallel (<8.6)", "mpi4py (<4.0)"] nvidia = ["cupy-cuda110", "dask-cuda", "dask-labextension", "fsspec", "jupyterlab (>=3)", "jupyterlab-nvdashboard"] -[[package]] -name = "dill" -version = "0.3.6" -description = "serialize all of python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, - {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - -[[package]] -name = "distlib" -version = "0.3.6" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] - [[package]] name = "distributed" version = "2023.3.2.1" @@ -1111,18 +892,6 @@ tornado = ">=6.0.3" urllib3 = ">=1.24.3" zict = ">=2.1.0" -[[package]] -name = "docutils" -version = "0.18.1" -description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, -] - [[package]] name = "exceptiongroup" version = "1.1.1" @@ -1168,22 +937,6 @@ files = [ [package.extras] devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] -[[package]] -name = "filelock" -version = "3.12.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, - {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, -] - -[package.extras] -docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] - [[package]] name = "flake8" version = "5.0.4" @@ -1201,30 +954,6 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" -[[package]] -name = "flask" -version = "2.3.2" -description = "A simple framework for building complex web applications." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, - {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, -] - -[package.dependencies] -blinker = ">=1.6.2" -click = ">=8.1.3" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -itsdangerous = ">=2.1.2" -Jinja2 = ">=3.1.2" -Werkzeug = ">=2.3.3" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - [[package]] name = "fonttools" version = "4.39.3" @@ -1401,17 +1130,6 @@ python-dateutil = ">=2.8.1" [package.extras] dev = ["flake8", "markdown", "twine", "wheel"] -[[package]] -name = "gputil" -version = "1.4.0" -description = "GPUtil is a Python module for getting the GPU status from NVIDA GPUs using nvidia-smi." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "GPUtil-1.4.0.tar.gz", hash = "sha256:099e52c65e512cdfa8c8763fca67f5a5c2afb63469602d5dcb4d296b3661efb9"}, -] - [[package]] name = "griffe" version = "0.27.1" @@ -1484,21 +1202,6 @@ files = [ h5py = {version = ">=2.1", markers = "python_version >= \"3.3\""} numpy = {version = "*", markers = "python_version >= \"3.4\""} -[[package]] -name = "identify" -version = "2.5.23" -description = "File identification library for Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "identify-2.5.23-py2.py3-none-any.whl", hash = "sha256:17d9351c028a781456965e781ed2a435755cac655df1ebd930f7186b54399312"}, - {file = "identify-2.5.23.tar.gz", hash = "sha256:50b01b9d5f73c6b53e5fa2caf9f543d3e657a9d0bbdeb203ebb8d45960ba7433"}, -] - -[package.extras] -license = ["ukkonen"] - [[package]] name = "idna" version = "3.4" @@ -1543,18 +1246,6 @@ pyav = ["av"] test = ["fsspec[github]", "pytest", "pytest-cov"] tifffile = ["tifffile"] -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - [[package]] name = "importlib-metadata" version = "6.6.0" @@ -1725,18 +1416,6 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] - [[package]] name = "jedi" version = "0.18.2" @@ -2162,22 +1841,6 @@ files = [ {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, ] -[[package]] -name = "lazy-loader" -version = "0.2" -description = "lazy_loader" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "lazy_loader-0.2-py3-none-any.whl", hash = "sha256:c35875f815c340f823ce3271ed645045397213f961b40ad0c0d395c3f5218eeb"}, - {file = "lazy_loader-0.2.tar.gz", hash = "sha256:0edc7a5175c400acb108f283749951fefdadedeb00adcec6e88b974a9254f18a"}, -] - -[package.extras] -lint = ["pre-commit (>=3.1)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - [[package]] name = "locket" version = "1.0.0" @@ -2190,18 +1853,6 @@ files = [ {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, ] -[[package]] -name = "lockfile" -version = "0.12.2" -description = "Platform-independent file locking module" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, - {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, -] - [[package]] name = "markdown" version = "3.3.7" @@ -2709,33 +2360,6 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] -[[package]] -name = "multiprocess" -version = "0.70.14" -description = "better multiprocessing and multithreading in python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multiprocess-0.70.14-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560a27540daef4ce8b24ed3cc2496a3c670df66c96d02461a4da67473685adf3"}, - {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:bfbbfa36f400b81d1978c940616bc77776424e5e34cb0c94974b178d727cfcd5"}, - {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:89fed99553a04ec4f9067031f83a886d7fdec5952005551a896a4b6a59575bb9"}, - {file = "multiprocess-0.70.14-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:40a5e3685462079e5fdee7c6789e3ef270595e1755199f0d50685e72523e1d2a"}, - {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_i686.whl", hash = "sha256:44936b2978d3f2648727b3eaeab6d7fa0bedf072dc5207bf35a96d5ee7c004cf"}, - {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e628503187b5d494bf29ffc52d3e1e57bb770ce7ce05d67c4bbdb3a0c7d3b05f"}, - {file = "multiprocess-0.70.14-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0d5da0fc84aacb0e4bd69c41b31edbf71b39fe2fb32a54eaedcaea241050855c"}, - {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_i686.whl", hash = "sha256:6a7b03a5b98e911a7785b9116805bd782815c5e2bd6c91c6a320f26fd3e7b7ad"}, - {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:cea5bdedd10aace3c660fedeac8b087136b4366d4ee49a30f1ebf7409bce00ae"}, - {file = "multiprocess-0.70.14-py310-none-any.whl", hash = "sha256:7dc1f2f6a1d34894c8a9a013fbc807971e336e7cc3f3ff233e61b9dc679b3b5c"}, - {file = "multiprocess-0.70.14-py37-none-any.whl", hash = "sha256:93a8208ca0926d05cdbb5b9250a604c401bed677579e96c14da3090beb798193"}, - {file = "multiprocess-0.70.14-py38-none-any.whl", hash = "sha256:6725bc79666bbd29a73ca148a0fb5f4ea22eed4a8f22fce58296492a02d18a7b"}, - {file = "multiprocess-0.70.14-py39-none-any.whl", hash = "sha256:63cee628b74a2c0631ef15da5534c8aedbc10c38910b9c8b18dcd327528d1ec7"}, - {file = "multiprocess-0.70.14.tar.gz", hash = "sha256:3eddafc12f2260d27ae03fe6069b12570ab4764ab59a75e81624fac453fbf46a"}, -] - -[package.dependencies] -dill = ">=0.3.6" - [[package]] name = "mypy" version = "0.990" @@ -2950,40 +2574,6 @@ files = [ {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, ] -[[package]] -name = "networkx" -version = "3.1" -description = "Python package for creating and manipulating graphs and networks" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, - {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, -] - -[package.extras] -default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] -developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] -test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "nodeenv" -version = "1.7.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, -] - -[package.dependencies] -setuptools = "*" - [[package]] name = "notebook" version = "6.5.4" @@ -3281,22 +2871,6 @@ files = [ docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] -[[package]] -name = "plotly" -version = "5.14.1" -description = "An open-source, interactive data visualization library for Python" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "plotly-5.14.1-py2.py3-none-any.whl", hash = "sha256:a63f3ad9e4cc2e02902a738e5e3e7f3d1307f2732ac71a6c28f1238ed3052826"}, - {file = "plotly-5.14.1.tar.gz", hash = "sha256:bcac86d7fcba3eff7260c1eddc36ca34dae2aded10a0709808446565e0e53b93"}, -] - -[package.dependencies] -packaging = "*" -tenacity = ">=6.2.0" - [[package]] name = "pluggy" version = "1.0.0" @@ -3313,25 +2887,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "pre-commit" -version = "3.3.1" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pre_commit-3.3.1-py2.py3-none-any.whl", hash = "sha256:218e9e3f7f7f3271ebc355a15598a4d3893ad9fc7b57fe446db75644543323b9"}, - {file = "pre_commit-3.3.1.tar.gz", hash = "sha256:733f78c9a056cdd169baa6cd4272d51ecfda95346ef8a89bf93712706021b907"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - [[package]] name = "prometheus-client" version = "0.16.0" @@ -3428,18 +2983,6 @@ files = [ {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, ] -[[package]] -name = "py-libnuma" -version = "1.2" -description = "Python3 libnuma ctypes wrapper" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "py-libnuma-1.2.tar.gz", hash = "sha256:e9d67ad04f274015e9acac497d642ce92f8a96d1d4e7be24e39124b24320b95a"}, - {file = "py_libnuma-1.2-py3-none-any.whl", hash = "sha256:a4c0b9188c03a1ba23b994e9c99f67ef25a090fadce90db94d46e229bd258fb9"}, -] - [[package]] name = "pycodestyle" version = "2.9.1" @@ -3653,27 +3196,6 @@ files = [ docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-virtualenv", "types-setuptools"] -[[package]] -name = "python-daemon" -version = "3.0.1" -description = "Library to implement a well-behaved Unix daemon process." -category = "dev" -optional = false -python-versions = ">=3" -files = [ - {file = "python-daemon-3.0.1.tar.gz", hash = "sha256:6c57452372f7eaff40934a1c03ad1826bf5e793558e87fef49131e6464b4dae5"}, - {file = "python_daemon-3.0.1-py3-none-any.whl", hash = "sha256:42bb848a3260a027fa71ad47ecd959e471327cb34da5965962edd5926229f341"}, -] - -[package.dependencies] -docutils = "*" -lockfile = ">=0.10" -setuptools = ">=62.4.0" - -[package.extras] -devel = ["coverage", "docutils", "isort", "testscenarios (>=0.4)", "testtools", "twine"] -test = ["coverage", "docutils", "testscenarios (>=0.4)", "testtools"] - [[package]] name = "python-dateutil" version = "2.8.2" @@ -3719,44 +3241,6 @@ typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] numpy = ["numpy (>=1.6.0)"] -[[package]] -name = "pywavelets" -version = "1.4.1" -description = "PyWavelets, wavelet transform module" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c"}, - {file = "PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4"}, - {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c"}, - {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202"}, - {file = "PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd"}, - {file = "PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b"}, - {file = "PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875"}, - {file = "PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de"}, - {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e"}, - {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784"}, - {file = "PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1"}, - {file = "PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc"}, - {file = "PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966"}, - {file = "PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa"}, - {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc"}, - {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4"}, - {file = "PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd"}, - {file = "PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2"}, - {file = "PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6"}, - {file = "PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426"}, - {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b"}, - {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356"}, - {file = "PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c"}, - {file = "PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4"}, - {file = "PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93"}, -] - -[package.dependencies] -numpy = ">=1.17.3" - [[package]] name = "pywin32" version = "306" @@ -4099,109 +3583,6 @@ files = [ {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, ] -[[package]] -name = "scikit-image" -version = "0.19.3" -description = "Image processing in Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "scikit-image-0.19.3.tar.gz", hash = "sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450"}, - {file = "scikit_image-0.19.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8"}, - {file = "scikit_image-0.19.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc"}, - {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19"}, - {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7"}, - {file = "scikit_image-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9"}, - {file = "scikit_image-0.19.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6"}, - {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f"}, - {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92"}, - {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2"}, - {file = "scikit_image-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732"}, - {file = "scikit_image-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe"}, - {file = "scikit_image-0.19.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef"}, - {file = "scikit_image-0.19.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099"}, - {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790"}, - {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245"}, - {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b"}, - {file = "scikit_image-0.19.3-cp38-cp38-win32.whl", hash = "sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34"}, - {file = "scikit_image-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d"}, - {file = "scikit_image-0.19.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced"}, - {file = "scikit_image-0.19.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d"}, - {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6"}, - {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83"}, - {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5"}, - {file = "scikit_image-0.19.3-cp39-cp39-win32.whl", hash = "sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e"}, - {file = "scikit_image-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919"}, -] - -[package.dependencies] -imageio = ">=2.4.1" -networkx = ">=2.2" -numpy = ">=1.17.0" -packaging = ">=20.0" -pillow = ">=6.1.0,<7.1.0 || >7.1.0,<7.1.1 || >7.1.1,<8.3.0 || >8.3.0" -PyWavelets = ">=1.1.1" -scipy = ">=1.4.1" -tifffile = ">=2019.7.26" - -[package.extras] -data = ["pooch (>=1.3.0)"] -docs = ["cloudpickle (>=0.2.1)", "dask[array] (>=0.15.0,!=2.17.0)", "ipywidgets", "kaleido", "matplotlib (>=3.3)", "myst-parser", "numpydoc (>=1.0)", "pandas (>=0.23.0)", "plotly (>=4.14.0)", "pooch (>=1.3.0)", "pytest-runner", "scikit-learn", "seaborn (>=0.7.1)", "sphinx (>=1.8)", "sphinx-copybutton", "sphinx-gallery (>=0.10.1)", "tifffile (>=2020.5.30)"] -optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pyamg", "qtpy"] -test = ["asv", "codecov", "flake8", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"] - -[[package]] -name = "scikit-image" -version = "0.20.0" -description = "Image processing in Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "scikit_image-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3cec8c5e8412ee19642a916648144186eb6b60c39fb6608ab478b4d1a4575e25"}, - {file = "scikit_image-0.20.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0ab378822fadc93db7e917a266d489ea33df3b42edfef197caaebbabbc2e4ecc"}, - {file = "scikit_image-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6797e3ef5fc53897bde131cfc3ceba6ce247d89cfe194fc8d3aba7f5c12aaf6"}, - {file = "scikit_image-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f667dcf01737248bc5bd0a99fad58475abeb6b6a8229aecee9fdb96cf988ae85"}, - {file = "scikit_image-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:79a400ffe35fc7f64d1d043f3d043e062015689ad5637c35cd5569edae87ae13"}, - {file = "scikit_image-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:049d955869620453b9e0568c2da62c8fec47bf3714be48b5d46bbaebb91bdc1f"}, - {file = "scikit_image-0.20.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:a503ee85b444234ee88f34bf8674872dc37c6124ff60b7eb9242813de012ff4e"}, - {file = "scikit_image-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3943d7355d02b40c066fd87cd5fe1b4f6637a16448e62333c4191a65ebf40a1c"}, - {file = "scikit_image-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d719242ea7e7250d49e38d1e33c44c2dd59c3414ae085881d168b98cbb6059a"}, - {file = "scikit_image-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:fdd1fd258e78c86e382fd687177431088a40880bd785e0ab40ee5f3794366710"}, - {file = "scikit_image-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1cd0486cb769d906307a3ec3884630be822d8ec2f41069e197336f904f584a33"}, - {file = "scikit_image-0.20.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:2e9026161d0a698f532352dda6455a0bc13b1c9d831ea9279726b59d064df574"}, - {file = "scikit_image-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c123e6b0677dc1697c04b5bf2efb7110bcca511b4bc6967a38fa395ae5edf44"}, - {file = "scikit_image-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76f2fd12b537daea806a078df9ea76f5cc5a529d5bd7c41d7d0a101e9c5f91c4"}, - {file = "scikit_image-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:2118d610096754bca44b5d37328e1382e5fa7c6493803685100c9238e257d848"}, - {file = "scikit_image-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:13a5c1c81ee5bcb64ee8ca8f1a2cf371b0c4345ea6fb67c3052e1c6d5edbd936"}, - {file = "scikit_image-0.20.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:1794889d2dbb385c7ad5656363371ba0057b7a3335cda093a11415af84bb96e2"}, - {file = "scikit_image-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df14f8a55dae511749b081d9402ea215ea7c641bd6f74f06aa7b623e132817df"}, - {file = "scikit_image-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b856efc75e3051bea6d40a8ffcdaabd5682783ece1aa91c3f6777c3372a98ca1"}, - {file = "scikit_image-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:a600374394b76b7fc260cef54e1be21047c4de0ecffb0b7f2f7392cd8ba16ffa"}, - {file = "scikit_image-0.20.0.tar.gz", hash = "sha256:2cd784fce18bd31d71ade62c6221440199ead03acf7544086261ee032264cf61"}, -] - -[package.dependencies] -imageio = ">=2.4.1" -lazy_loader = ">=0.1" -networkx = ">=2.8" -numpy = ">=1.21.1" -packaging = ">=20.0" -pillow = ">=9.0.1" -PyWavelets = ">=1.1.1" -scipy = {version = ">=1.8", markers = "python_version > \"3.9\""} -tifffile = ">=2019.7.26" - -[package.extras] -build = ["Cython (>=0.29.24)", "build", "meson-python (>=0.13.0rc0)", "ninja", "numpy (>=1.21.1)", "packaging (>=20)", "pythran", "setuptools (>=67)", "wheel"] -data = ["pooch (>=1.3.0)"] -default = ["PyWavelets (>=1.1.1)", "imageio (>=2.4.1)", "lazy_loader (>=0.1)", "networkx (>=2.8)", "numpy (>=1.21.1)", "packaging (>=20.0)", "pillow (>=9.0.1)", "scipy (>=1.8)", "scipy (>=1.8,<1.9.2)", "tifffile (>=2019.7.26)"] -developer = ["pre-commit", "rtoml"] -docs = ["dask[array] (>=2022.9.2)", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.5)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pytest-runner", "scikit-learn", "seaborn (>=0.11)", "sphinx (>=5.2)", "sphinx-copybutton", "sphinx-gallery (>=0.11)", "tifffile (>=2022.8.12)"] -optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.3)", "pooch (>=1.3.0)", "pyamg"] -test = ["asv", "codecov", "matplotlib (>=3.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"] - [[package]] name = "scipy" version = "1.10.1" @@ -4258,23 +3639,6 @@ nativelib = ["pyobjc-framework-Cocoa", "pywin32"] objc = ["pyobjc-framework-Cocoa"] win32 = ["pywin32"] -[[package]] -name = "setuptools" -version = "67.7.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, - {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -4299,18 +3663,6 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - [[package]] name = "sortedcontainers" version = "2.4.0" @@ -4335,172 +3687,6 @@ files = [ {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] -[[package]] -name = "sphinx" -version = "6.2.1" -description = "Python documentation generator" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] - -[[package]] -name = "sphinx-rtd-theme" -version = "1.2.0" -description = "Read the Docs theme for Sphinx" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "sphinx_rtd_theme-1.2.0-py2.py3-none-any.whl", hash = "sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2"}, - {file = "sphinx_rtd_theme-1.2.0.tar.gz", hash = "sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8"}, -] - -[package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<7" -sphinxcontrib-jquery = {version = ">=2.0.0,<3.0.0 || >3.0.0", markers = "python_version > \"3\""} - -[package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -category = "dev" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] name = "stack-data" version = "0.6.2" @@ -4548,21 +3734,6 @@ files = [ {file = "tblib-1.7.0.tar.gz", hash = "sha256:059bd77306ea7b419d4f76016aef6d7027cc8a0785579b5aad198803435f882c"}, ] -[[package]] -name = "tenacity" -version = "8.2.2" -description = "Retry code until it succeeds" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"}, - {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"}, -] - -[package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] - [[package]] name = "terminado" version = "0.17.1" @@ -4584,24 +3755,6 @@ tornado = ">=6.1.0" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] -[[package]] -name = "tifffile" -version = "2023.4.12" -description = "Read and write TIFF files" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tifffile-2023.4.12-py3-none-any.whl", hash = "sha256:3161954746fe32c4f4244d0fb2eb0a272f3a3760b78882a42faa83ac5e6e0b74"}, - {file = "tifffile-2023.4.12.tar.gz", hash = "sha256:2fa99f9890caab919d932a0acaa9d0f5843dc2ef3594e212963932e20713badd"}, -] - -[package.dependencies] -numpy = "*" - -[package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2023.1.23)", "lxml", "matplotlib", "zarr"] - [[package]] name = "tinycss2" version = "1.2.1" @@ -4747,72 +3900,6 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "uvloop" -version = "0.17.0" -description = "Fast implementation of asyncio event loop on top of libuv" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, - {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, - {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, - {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, - {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, - {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, - {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, - {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, - {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, - {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, - {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, - {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, - {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, - {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, - {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, - {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, - {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, - {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, - {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, - {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, - {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, - {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, - {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, - {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, - {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, - {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, - {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, - {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, - {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, - {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, -] - -[package.extras] -dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] - -[[package]] -name = "virtualenv" -version = "20.23.0" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"}, - {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"}, -] - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.11,<4" -platformdirs = ">=3.2,<4" - -[package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] - [[package]] name = "watchdog" version = "3.0.0" @@ -4910,24 +3997,6 @@ docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] -[[package]] -name = "werkzeug" -version = "2.3.3" -description = "The comprehensive WSGI web application library." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Werkzeug-2.3.3-py3-none-any.whl", hash = "sha256:4866679a0722de00796a74086238bb3b98d90f423f05de039abb09315487254a"}, - {file = "Werkzeug-2.3.3.tar.gz", hash = "sha256:a987caf1092edc7523edb139edb20c70571c4a8d5eed02e0b547b4739174d091"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - [[package]] name = "y-py" version = "0.5.9" @@ -5055,4 +4124,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "f41458cb9df30c2d794654d124813d617ad360116bdb31437cf8a357069afe9c" +content-hash = "b52da7c5e9712d7798f124f954c106af76b11366f3f044ec4f35df09f8e392bc" diff --git a/pyproject.toml b/pyproject.toml index 2f302f68..d092b31e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,41 +28,11 @@ matplotlib = "^3.5.3" imageio = "^2.21.2" jupyterlab = "^3.4.6" devito = "^4.8.1" -scipy = "^1.9.2" +scipy = "^1.9.1" hdf5storage = "^0.1.18" psutil = "^5.9.0" nest-asyncio = "^1.5.6" -[tool.poetry.group.stride.dependencies] -blosc = "*" -cached-property = "*" -click = "*" -cloudpickle = ">=1.6" -cython = "*" -dash = ">=2.0.0" -flake8 = "*" -gputil = "*" -h5py = ">=3.2" -multiprocess = "*" -nbval = "*" -numpy = ">=1.20" -pre_commit = "*" -psutil = "*" -py-libnuma = "*" -pyflakes = "*" -pytest = "*" -pytest-cov = "*" -pytest-runner = "*" -python-daemon = "*" -pyyaml = ">=5.4" -pyzmq = ">=22.1" -scikit-image = "*" -scipy = ">=1.6" -sphinx = "*" -sphinx_rtd_theme = "*" -uvloop = "*" -zict = "*" - [tool.poetry.group.dev.dependencies] black = "^22.8.0" flake8 = "^5.0.4" From d6466e9f4a65bcccae08cf2fa03fd50a0e766704 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Wed, 3 May 2023 10:48:56 +0000 Subject: [PATCH 010/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a5ac0a61..16a2a317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.10" +version = "v0.0.11" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 9db24bf07f09d6c0b2c4555c4e7a2972ebdb1170 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 3 May 2023 08:31:42 -0300 Subject: [PATCH 011/198] Adding frozenset to main dependencies --- poetry.lock | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index ca10872c..83345810 100644 --- a/poetry.lock +++ b/poetry.lock @@ -996,7 +996,7 @@ files = [ name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4124,4 +4124,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "b52da7c5e9712d7798f124f954c106af76b11366f3f044ec4f35df09f8e392bc" +content-hash = "2515a037e14c0c912427311ecbdba6ba2302085b473af925f20f70b0ab60b823" diff --git a/pyproject.toml b/pyproject.toml index 16a2a317..af5660c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,11 +32,11 @@ scipy = "^1.9.1" hdf5storage = "^0.1.18" psutil = "^5.9.0" nest-asyncio = "^1.5.6" +frozenlist = "^1.3.3" [tool.poetry.group.dev.dependencies] black = "^22.8.0" flake8 = "^5.0.4" -frozenlist = "^1.3.3" isort = "^5.10.1" mypy = "^0.990" pytest = "^7.2.0" From 981f50e5067e657b8303ca1d47eaf3675fb2b4c8 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Wed, 3 May 2023 11:32:51 +0000 Subject: [PATCH 012/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index af5660c0..313c75d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.11" +version = "v0.0.12" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 858d345a8f0757c7adb2b09b2073d7b79a6e6471 Mon Sep 17 00:00:00 2001 From: Rob Luke Date: Thu, 4 May 2023 00:33:17 +1000 Subject: [PATCH 013/198] Update README with text from docs landing page (#4) --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c986dcc0..45315045 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ -# Neurotech Development Kit (NDK) Research +# Neurotech Development Kit (NDK) + +The _Neurotech Development Kit_ (NDK) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. +Featuring an easy-to-use API and pre-built examples, the NDK provides a seamless starting point for users. +Moreover, the NDK offers educational resources, such as interactive simulations and notebook-based tutorials, catering to a diverse audience including researchers, educators, engineers, and trainees. +By lowering the barrier of entry for newcomers and accelerating the progress of researchers, the NDK aims to be a versatile and invaluable tool for the neurotech community. + +The initial set of target users for the NDK are ultrasound simulation trainees – individuals with backgrounds in technical or neuroscience-related fields who are learning to perform ultrasound simulations. +Our goal is to help users familiarize themselves with ultrasound simulation, understand the importance of input parameters, and streamline the process of running and visualizing simulations. +In the future, we plan to expand the NDK's features to incorporate additional functionality and modalities, catering to a broader range of users, including ultrasound researchers, product developers, machine learning engineers, and many more. + +The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. +The Neurotech Development Kit is actively developed and we welcome feedback and contributions. + ## Exploring the Repository From c005e2503ae0c501414692ac45e5bd18c1cc79a5 Mon Sep 17 00:00:00 2001 From: d-lucena Date: Wed, 3 May 2023 14:34:12 +0000 Subject: [PATCH 014/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 313c75d4..56792153 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.12" +version = "v0.0.13" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From b58b12975d72fcd2fd4e0f0fa6d3b38b20aeddbe Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 3 May 2023 13:16:53 -0300 Subject: [PATCH 015/198] Downgrading mkdocs gallery --- poetry.lock | 32 ++++++++++++++++---------------- pyproject.toml | 1 + 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 83345810..968423ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1116,7 +1116,7 @@ tqdm = ["tqdm"] name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." -category = "dev" +category = "main" optional = false python-versions = "*" files = [ @@ -1857,7 +1857,7 @@ files = [ name = "markdown" version = "3.3.7" description = "Python implementation of Markdown." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2025,7 +2025,7 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2049,7 +2049,7 @@ files = [ name = "mkdocs" version = "1.4.3" description = "Project documentation with Markdown." -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2092,14 +2092,14 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-gallery" -version = "0.7.7" +version = "0.7.6" description = "a `mkdocs` plugin to generate example galleries from python scripts, similar to `sphinx-gallery`." -category = "dev" +category = "main" optional = false python-versions = "*" files = [ - {file = "mkdocs-gallery-0.7.7.tar.gz", hash = "sha256:c9758ba30eef437708d6e12a417f0d56f24aeeb194d664d5dbf7c394f6ddc58c"}, - {file = "mkdocs_gallery-0.7.7-py2.py3-none-any.whl", hash = "sha256:f0867751780d625c9b56bffd87174e77d1458acc87765254344e952f19b3025b"}, + {file = "mkdocs-gallery-0.7.6.tar.gz", hash = "sha256:cf6cd5b548c4c7112ebea892462d773c1afe9d3114be226e38509ea0a0dafde8"}, + {file = "mkdocs_gallery-0.7.6-py2.py3-none-any.whl", hash = "sha256:69e9c87714e8ec461343e8516ad9938ce080360702f16bcd06754fda7cb9e5ca"}, ] [package.dependencies] @@ -2111,7 +2111,7 @@ tqdm = "*" name = "mkdocs-material" version = "9.1.9" description = "Documentation that simply works" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2134,7 +2134,7 @@ requests = ">=2.26" name = "mkdocs-material-extensions" version = "1.1.1" description = "Extension pack for Python Markdown and MkDocs Material." -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3038,7 +3038,7 @@ plugins = ["importlib-metadata"] name = "pymdown-extensions" version = "9.11" description = "Extension pack for Python Markdown." -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3335,7 +3335,7 @@ files = [ name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3440,7 +3440,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "regex" version = "2023.5.4" description = "Alternative regular expression module, to replace re." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3823,7 +3823,7 @@ files = [ name = "tqdm" version = "4.65.0" description = "Fast, Extensible Progress Meter" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3904,7 +3904,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "watchdog" version = "3.0.0" description = "Filesystem events monitoring" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4124,4 +4124,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "2515a037e14c0c912427311ecbdba6ba2302085b473af925f20f70b0ab60b823" +content-hash = "98af7dcee36fe6205caf365f624ca3f65db861f4fac6497aa0e0e3dc0ac3c1ad" diff --git a/pyproject.toml b/pyproject.toml index 56792153..fac2593f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ hdf5storage = "^0.1.18" psutil = "^5.9.0" nest-asyncio = "^1.5.6" frozenlist = "^1.3.3" +mkdocs-gallery = "0.7.6" [tool.poetry.group.dev.dependencies] black = "^22.8.0" From 5eb47ff348909f26294e83901e0aa8dd8f2b69df Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Wed, 3 May 2023 16:44:25 +0000 Subject: [PATCH 016/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fac2593f..6796440f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.13" +version = "v0.0.14" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From dafdbef32b10c39199a67acd9b922f1ee1cf3340 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 3 May 2023 13:53:15 -0300 Subject: [PATCH 017/198] Adding bounds to operator. Fixing devito version --- poetry.lock | 98 +++++++++++--------- pyproject.toml | 2 +- src/neurotechdevkit/scenarios/_base.py | 41 ++++++++ tests/neurotechdevkit/scenarios/test_base.py | 28 ++++++ 4 files changed, 126 insertions(+), 43 deletions(-) diff --git a/poetry.lock b/poetry.lock index 968423ff..ce78e1a4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -528,6 +528,22 @@ files = [ {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, ] +[[package]] +name = "codecov" +version = "2.1.13" +description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5"}, + {file = "codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c"}, +] + +[package.dependencies] +coverage = "*" +requests = ">=2.7.9" + [[package]] name = "codepy" version = "2019.1" @@ -745,33 +761,31 @@ files = [ [[package]] name = "dask" -version = "2023.3.2" +version = "2022.6.1" description = "Parallel PyData with Task Scheduling" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "dask-2023.3.2-py3-none-any.whl", hash = "sha256:5e64763d62feb18afd3ad66f364e0b4f456f7ac92e894fcc87950af75029ecdf"}, - {file = "dask-2023.3.2.tar.gz", hash = "sha256:51009e92ba9a280bd417633d1ae84f3ed23a8940f0a19594a4b7797ef226fff4"}, + {file = "dask-2022.6.1-py3-none-any.whl", hash = "sha256:fbd2707070ee8cba080a297301c3984de3d0dee211f30c81387d5fa8adc7de74"}, + {file = "dask-2022.6.1.tar.gz", hash = "sha256:6ecc4571da3e5575dd1fa1537237434908f81b929f7f2a3493a7f6eb8507903e"}, ] [package.dependencies] -click = ">=7.0" cloudpickle = ">=1.1.1" fsspec = ">=0.6.0" -importlib-metadata = ">=4.13.0" packaging = ">=20.0" -partd = ">=1.2.0" +partd = ">=0.3.10" pyyaml = ">=5.3.1" toolz = ">=0.8.2" [package.extras] -array = ["numpy (>=1.21)"] -complete = ["bokeh (>=2.4.2,<3)", "distributed (==2023.3.2)", "jinja2 (>=2.10.3)", "lz4 (>=4.3.2)", "numpy (>=1.21)", "pandas (>=1.3)", "pyarrow (>=7.0)"] -dataframe = ["numpy (>=1.21)", "pandas (>=1.3)"] -diagnostics = ["bokeh (>=2.4.2,<3)", "jinja2 (>=2.10.3)"] -distributed = ["distributed (==2023.3.2)"] -test = ["pandas[test]", "pre-commit", "pytest", "pytest-rerunfailures", "pytest-xdist"] +array = ["numpy (>=1.18)"] +complete = ["bokeh (>=2.4.2)", "distributed (==2022.6.1)", "jinja2", "numpy (>=1.18)", "pandas (>=1.0)"] +dataframe = ["numpy (>=1.18)", "pandas (>=1.0)"] +diagnostics = ["bokeh (>=2.4.2)", "jinja2"] +distributed = ["distributed (==2022.6.1)"] +test = ["pre-commit", "pytest", "pytest-rerunfailures", "pytest-xdist"] [[package]] name = "debugpy" @@ -827,14 +841,14 @@ files = [ [[package]] name = "devito" -version = "4.8.1" +version = "4.7.1" description = "Finite Difference DSL for symbolic computation." category = "main" optional = false python-versions = "*" files = [ - {file = "devito-4.8.1-py3-none-any.whl", hash = "sha256:f6463354e9be74e54a00e6e522fbb888026df0c04ad2d54a1271708efff7271d"}, - {file = "devito-4.8.1.tar.gz", hash = "sha256:56d0957a3226ed2a81c408107a614f04faa896d42c83a8b2bd1c8b1100adf51d"}, + {file = "devito-4.7.1-py3-none-any.whl", hash = "sha256:7ca48b79a92f01eb36cf0d6dfdc9bf1d26d49ed6f9eaf38933b72cceef559500"}, + {file = "devito-4.7.1.tar.gz", hash = "sha256:bb7bf65b423a15bfb7e31a54fc0d82070ee59ce021e02bcd3a0aa7df1db9b25a"}, ] [package.dependencies] @@ -842,55 +856,56 @@ anytree = ">=2.4.3,<=2.8" cached-property = "*" cgen = ">=2020.1" click = "<9.0" +codecov = "*" codepy = ">=2019.1" -distributed = "<2023.4" +distributed = "<2022.8" flake8 = ">=2.1.0" multidict = "*" nbval = "*" numpy = ">1.16" pip = ">=9.0.1" psutil = ">=5.1.0,<6.0" -py-cpuinfo = "<10" +py-cpuinfo = "<=8" pyrevolve = ">=2.1.3" -pytest = ">=7.2,<8.0" +pytest = ">=3.6,<8.0" pytest-cov = "*" pytest-runner = "*" scipy = "*" -sympy = ">=1.9,<1.12" +sympy = ">=1.7,<1.11" [package.extras] extras = ["matplotlib", "pandas"] -mpi = ["ipyparallel (<8.6)", "mpi4py (<4.0)"] +mpi = ["ipyparallel (<8.5)", "mpi4py (<4.0)"] nvidia = ["cupy-cuda110", "dask-cuda", "dask-labextension", "fsspec", "jupyterlab (>=3)", "jupyterlab-nvdashboard"] [[package]] name = "distributed" -version = "2023.3.2.1" +version = "2022.6.1" description = "Distributed scheduler for Dask" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "distributed-2023.3.2.1-py3-none-any.whl", hash = "sha256:a7756a4b952ec5a7fd3163e93aef99aaf8b0000568fa9ee7c000113a470d7f8e"}, - {file = "distributed-2023.3.2.1.tar.gz", hash = "sha256:f40c4578622a15261bb59676ca8d7024f7d108aecc58406a5482bdfb7e69ce99"}, + {file = "distributed-2022.6.1-py3-none-any.whl", hash = "sha256:4c614050e86ac8a0230418625f313a71dffdcd9cfb3b7128a33b7faa611a686c"}, + {file = "distributed-2022.6.1.tar.gz", hash = "sha256:c4efd70aeb072725e683bca1c2de8173cd577018a7a5c3795886a5691e70bc37"}, ] [package.dependencies] -click = ">=8.0" +click = ">=6.6" cloudpickle = ">=1.5.0" -dask = "2023.3.2" -jinja2 = ">=2.10.3" +dask = "2022.6.1" +jinja2 = "*" locket = ">=1.0.0" -msgpack = ">=1.0.0" +msgpack = ">=0.6.0" packaging = ">=20.0" -psutil = ">=5.7.0" -pyyaml = ">=5.3.1" -sortedcontainers = ">=2.0.5" +psutil = ">=5.0" +pyyaml = "*" +sortedcontainers = "<2.0.0 || >2.0.0,<2.0.1 || >2.0.1" tblib = ">=1.6.0" -toolz = ">=0.10.0" +toolz = ">=0.8.2" tornado = ">=6.0.3" -urllib3 = ">=1.24.3" -zict = ">=2.1.0" +urllib3 = "*" +zict = ">=0.1.3" [[package]] name = "exceptiongroup" @@ -2973,14 +2988,13 @@ tests = ["pytest"] [[package]] name = "py-cpuinfo" -version = "9.0.0" -description = "Get CPU info with pure Python" +version = "8.0.0" +description = "Get CPU info with pure Python 2 & 3" category = "main" optional = false python-versions = "*" files = [ - {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, - {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, + {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, ] [[package]] @@ -3709,14 +3723,14 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "sympy" -version = "1.11.1" +version = "1.10.1" description = "Computer algebra system (CAS) in Python" category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"}, - {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"}, + {file = "sympy-1.10.1-py3-none-any.whl", hash = "sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130"}, + {file = "sympy-1.10.1.tar.gz", hash = "sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b"}, ] [package.dependencies] @@ -4124,4 +4138,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "98af7dcee36fe6205caf365f624ca3f65db861f4fac6497aa0e0e3dc0ac3c1ad" +content-hash = "4057c5c78346f4f421580da855b3b2bbc8474b42e0d97eb6a5ec5939c28e1695" diff --git a/pyproject.toml b/pyproject.toml index 6796440f..a3603698 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ numpy = "^1.23.3" matplotlib = "^3.5.3" imageio = "^2.21.2" jupyterlab = "^3.4.6" -devito = "^4.8.1" +devito = "4.7.1" scipy = "^1.9.1" hdf5storage = "^0.1.18" psutil = "^5.9.0" diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index 75456070..d5f7728b 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -433,6 +433,9 @@ def simulate_steady_state( traces = self._execute_pde( pde=pde, sub_problem=sub_problem, + save_bounds=self._get_steady_state_recording_time_bounds( + points_per_period, n_cycles_steady_state + ), save_undersampling=recording_time_undersampling, wavefield_slice=self._wavefield_slice(), n_jobs=n_jobs, @@ -589,6 +592,7 @@ def _simulate_pulse( traces = self._execute_pde( pde=pde, sub_problem=sub_problem, + save_bounds=self._get_pulsed_recording_time_bounds(), save_undersampling=recording_time_undersampling, wavefield_slice=self._wavefield_slice(slice_axis, slice_position), n_jobs=n_jobs, @@ -773,10 +777,45 @@ def _validate_slice_args( f"current range is {current_range}.", ) + def _get_steady_state_recording_time_bounds( + self, ppp: int, n_cycles: int + ) -> tuple[int, int]: + """Defines the indices bounding the period of time to be recorded. + + For steady-state simulations, we only want to keep the last few cycles of the + simulation. + + Args: + ppp: The number of points in time per phase to simulate for each cycle of + the wave. + n_cycles: The number of complete cycles to record at the end of the + simulation. + + Returns: + A tuple containing the time indices bounding the period of time which should + be recorded for a steady-state simulation. + """ + n_frames = ppp * n_cycles + time = self.problem.time + return (time.num - n_frames, time.num - 1) + + def _get_pulsed_recording_time_bounds(self) -> tuple[int, int]: + """Defines the indices bounding the period of time to be recorded. + + For pulsed simulations, we want to keep the data from all timesteps. + + Returns: + A tuple containing the time indices bounding the period of time which should + be recorded for a pulsed simulation. + """ + time = self.problem.time + return (0, time.num - 1) + def _execute_pde( self, pde: stride.Operator, sub_problem: stride.SubProblem, + save_bounds: tuple[int, int], save_undersampling: int, wavefield_slice: tuple[slice, ...], n_jobs: int | None = None, @@ -787,6 +826,7 @@ def _execute_pde( pde: The `Operator` containing the PDE to execute. sub_problem: The `SubProblem` containing details of the source and waveform for the simulation. + save_bounds: The time indices bounding the period of time to be recorded. save_undersampling: The undersampling factor to apply to the time axis when recording simulation results. wavefield_slice: A tuple of slices defining the region of the grid to @@ -812,6 +852,7 @@ def _execute_pde( boundary_type="complex_frequency_shift_PML_2", diff_source=True, save_wavefield=True, + save_bounds=save_bounds, save_undersampling=save_undersampling, wavefield_slice=wavefield_slice, devito_args=devito_args, diff --git a/tests/neurotechdevkit/scenarios/test_base.py b/tests/neurotechdevkit/scenarios/test_base.py index 4c520ebf..6d5b3237 100644 --- a/tests/neurotechdevkit/scenarios/test_base.py +++ b/tests/neurotechdevkit/scenarios/test_base.py @@ -333,12 +333,14 @@ def test_execute_pde(tester_with_time, fake_pde, sim_mode): sub_problem = tester_with_time._setup_sub_problem( center_frequency=50.0, simulation_mode=sim_mode ) + test_save_bounds = (1, 100) test_undersampling = 7 test_wavefield_slice = (slice(2, 1000),) n_jobs = 237 result = tester_with_time._execute_pde( pde=fake_pde, sub_problem=sub_problem, + save_bounds=test_save_bounds, save_undersampling=test_undersampling, wavefield_slice=test_wavefield_slice, n_jobs=n_jobs, @@ -353,6 +355,7 @@ def test_execute_pde(tester_with_time, fake_pde, sim_mode): assert kwargs["boundary_type"] == "complex_frequency_shift_PML_2" assert kwargs["diff_source"] assert kwargs["save_wavefield"] + assert kwargs["save_bounds"] == test_save_bounds assert kwargs["save_undersampling"] == test_undersampling assert kwargs["wavefield_slice"] == test_wavefield_slice assert kwargs["devito_args"]["nthreads"] == n_jobs @@ -399,6 +402,24 @@ def test_wavefield_slice_selects_right_position( assert updated_slices[selected_axis + 1] == expected_updated_slice +def test_get_steady_state_recording_time_bounds(tester_with_time): + """Verify that expected time bounds for steady-state are returned.""" + time = tester_with_time.problem.time + expected_bounds = (time.num - 24, time.num - 1) + actual_bounds = tester_with_time._get_steady_state_recording_time_bounds( + ppp=8, n_cycles=3 + ) + assert actual_bounds == expected_bounds + + +def test_get_pulsed_recording_time_bounds(tester_with_time): + """Verify that expected time bounds for pulsed are returned.""" + time = tester_with_time.problem.time + expected_bounds = (0, time.num - 1) + actual_bounds = tester_with_time._get_pulsed_recording_time_bounds() + assert actual_bounds == expected_bounds + + def test_simulate_steady_state_pde_args(base_tester, fake_pde, monkeypatch): """Verify the pde call args from simulate_steady_state.""" monkeypatch.setattr(base_tester, "_create_pde", lambda: fake_pde) @@ -412,6 +433,10 @@ def test_simulate_steady_state_pde_args(base_tester, fake_pde, monkeypatch): ) pde_kwargs = fake_pde.last_kwargs + expected_bounds = base_tester._get_steady_state_recording_time_bounds( + test_ppp, test_n_cycles + ) + assert pde_kwargs["save_bounds"] == expected_bounds expected_wavefield = base_tester._wavefield_slice() assert pde_kwargs["wavefield_slice"] == expected_wavefield assert pde_kwargs["save_undersampling"] == test_undersampling @@ -458,6 +483,9 @@ def test_simulate_pulse_pde_args(base_tester, fake_pde, monkeypatch): ) pde_kwargs = fake_pde.last_kwargs + + expected_bounds = base_tester._get_pulsed_recording_time_bounds() + assert pde_kwargs["save_bounds"] == expected_bounds expected_wavefield = base_tester._wavefield_slice() assert pde_kwargs["wavefield_slice"] == expected_wavefield assert pde_kwargs["save_undersampling"] == test_undersampling From fa1779b717750af5a38dbaf3ca4e2a2cd1df8d70 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Wed, 3 May 2023 17:00:48 +0000 Subject: [PATCH 018/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a3603698..17e7f3e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.14" +version = "v0.0.15" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 8f6648f66e400acfef721b4b31f4d4d47448e134 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 3 May 2023 19:27:09 -0300 Subject: [PATCH 019/198] Mentioning libomp in the installation instructions --- README.md | 25 ++++++++++++++++++++----- docs/contributing.md | 18 ++++++++++++++++++ docs/index.md | 22 ++++++++++++++++++---- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 45315045..3c98b89d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ In the future, we plan to expand the NDK's features to incorporate additional fu The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. The Neurotech Development Kit is actively developed and we welcome feedback and contributions. - ## Exploring the Repository The NDK API was developed with easy-of-use in mind, and **3 lines of code is all you need to run a simulation**: @@ -38,16 +37,32 @@ result.render_steady_state_amplitudes() You can install the package using: -``` bash +```bash pip install neurotechdevkit ``` And then you must install stride using: -``` bash + +```bash pip install git+https://github.com/trustimaging/stride ``` -### Development +`devito`, a dependency of `neurotechdevkit`, requires `libomp` to perform its runtime compilation. It can be installed on *MacOS* with: + +``` +brew install libomp +``` + +You will also have to set an environment variable that defines what compiler `devito` will use, like so: + +``` +export DEVITO_ARCH=clang +``` + +the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` + + +## Development See our [contribution requirements](docs/contributing.md) for more information on how to install the package locally using poetry and on how to contribute. @@ -61,6 +76,6 @@ $ make test See the Makefile for other commands. -### Acknowledgements +## Acknowledgements Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. diff --git a/docs/contributing.md b/docs/contributing.md index cbf336d9..aadf6ef1 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -36,6 +36,8 @@ If you don't want to install NDK's dependencies on your machine, you can run it * Run the container, which will start a jupyter notebook server: ``` + git clone https://github.com/agencyenterprise/neurotechdevkit.git + cd neurotechdevkit docker compose up ``` @@ -62,6 +64,22 @@ Install stride with $ poetry run pip install git+https://github.com/trustimaging/stride ``` +`devito`, a dependency of `neurotechdevkit`, requires `libomp` to perform its runtime compilation. It can be installed on *MacOS* with: + +``` +brew install libomp +``` + +You will also have to set an environment variable that defines what compiler `devito` will use, like so: + +``` +export DEVITO_ARCH=clang +``` + +the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` + + + ### Using the environment If you are not already using a virtual environment, `poetry` will [create one for you by default](https://python-poetry.org/docs/basic-usage/#using-your-virtual-environment). You will need to use this virtual env when using or working on the package. diff --git a/docs/index.md b/docs/index.md index ad675a33..6d96145a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,13 +1,11 @@ # Welcome to Neurotech Development Kit - The _Neurotech Development Kit_ (NDK) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. Featuring an easy-to-use API and pre-built examples, the NDK provides a seamless starting point for users. Moreover, the NDK offers educational resources, such as interactive simulations and notebook-based tutorials, catering to a diverse audience including researchers, educators, engineers, and trainees. By lowering the barrier of entry for newcomers and accelerating the progress of researchers, the NDK aims to be a versatile and invaluable tool for the neurotech community. The initial set of target users for the NDK are ultrasound simulation trainees – individuals with backgrounds in technical or neuroscience-related fields who are learning to perform ultrasound simulations. Our goal is to help users familiarize themselves with ultrasound simulation, understand the importance of input parameters, and streamline the process of running and visualizing simulations. In the future, we plan to expand the NDK's features to incorporate additional functionality and modalities, catering to a broader range of users, including ultrasound researchers, product developers, machine learning engineers, and many more. The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. The Neurotech Development Kit is actively developed and we welcome feedback and contributions. -
![Simulation](images/ndk_example.gif){ width="900" }
@@ -20,16 +18,32 @@ The initial release of NDK provides support for transcranial functional ultrasou You can install the package using: -``` bash +```bash pip install neurotechdevkit ``` And then you must install stride using: -``` bash + +```bash pip install git+https://github.com/trustimaging/stride ``` +`devito`, a dependency of `neurotechdevkit`, requires `libomp` to perform its runtime compilation. It can be installed on _MacOS_ with: + +``` +brew install libomp +``` + +You will also have to set an environment variable that defines what compiler `devito` will use, like so: + +``` +export DEVITO_ARCH=clang +``` + +the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` + ### Docker + You can run `NDK` inside a docker container with a couple of steps: 1. Install [docker](https://docs.docker.com/engine/install/#desktop) From 39e47b7218b8b99d805b0f436660156bb4518286 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Thu, 4 May 2023 09:24:48 -0300 Subject: [PATCH 020/198] Mentioning libomp CPATH environment variable --- README.md | 31 +++++++++++++++++++++++-------- docs/contributing.md | 19 ++++++++++++++++--- docs/index.md | 26 +++++++++++++++++++++----- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3c98b89d..57eb8a82 100644 --- a/README.md +++ b/README.md @@ -33,36 +33,51 @@ result.render_steady_state_amplitudes() ## Setup -`neurotechdevkit` requires Python `>=3.9` and `<3.11` to be installed. You can find which Python version you have installed by running `python --version` in a terminal. If you don't have Python installed, or you are running an unsupported version, you can download it from [python.org](https://www.python.org/downloads/). Python environment managers like pyenv, conda, and poetry are all perfectly suitable as well. +`neurotechdevkit` requires Python `>=3.9` and `<3.11` to be installed. You can find which Python version you have installed by running `python --version` in a terminal. -You can install the package using: +If you don't have Python installed, or you are running an unsupported version, you can download it from [python.org](https://www.python.org/downloads/). Python environment managers like pyenv, conda, and poetry are all perfectly suitable as well. + +You can install the `neurotechdevkit` package using: ```bash pip install neurotechdevkit ``` -And then you must install stride using: +You also have to install stride, it can be done running: ```bash pip install git+https://github.com/trustimaging/stride ``` -`devito`, a dependency of `neurotechdevkit`, requires `libomp` to perform its runtime compilation. It can be installed on *MacOS* with: +`devito`, a dependency of `neurotechdevkit`, requires `libomp`. On MacOS it can be installed with: ``` brew install libomp ``` +the output of the command above will look like this: + +``` +For compilers to find libomp you may need to set: +export LDFLAGS="-L/usr/local/opt/libomp/lib" +export CPPFLAGS="-I/usr/local/opt/libomp/include" +``` + +`devito` requires the directory with `libomp` headers to be accessible during the runtime compilation, you can make it accessible by exporting a new environment variable `CPATH` with the path for libomp headers, like so: + +``` +export CPATH="/usr/local/opt/libomp/include" +``` + You will also have to set an environment variable that defines what compiler `devito` will use, like so: ``` -export DEVITO_ARCH=clang +export DEVITO_ARCH=gcc ``` the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` - -## Development +### Development See our [contribution requirements](docs/contributing.md) for more information on how to install the package locally using poetry and on how to contribute. @@ -76,6 +91,6 @@ $ make test See the Makefile for other commands. -## Acknowledgements +### Acknowledgements Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. diff --git a/docs/contributing.md b/docs/contributing.md index aadf6ef1..81c54c26 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -64,22 +64,35 @@ Install stride with $ poetry run pip install git+https://github.com/trustimaging/stride ``` -`devito`, a dependency of `neurotechdevkit`, requires `libomp` to perform its runtime compilation. It can be installed on *MacOS* with: +`devito`, a dependency of `neurotechdevkit`, requires `libomp`. On MacOS it can be installed with: ``` brew install libomp ``` +the output of the command above will look like this: + +``` +For compilers to find libomp you may need to set: +export LDFLAGS="-L/usr/local/opt/libomp/lib" +export CPPFLAGS="-I/usr/local/opt/libomp/include" +``` + +`devito` requires the directory with `libomp` headers to be accessible during the runtime compilation, you can make it accessible by exporting a new environment variable `CPATH` with the path for libomp headers, like so: + +``` +export CPATH="/usr/local/opt/libomp/include" +``` + You will also have to set an environment variable that defines what compiler `devito` will use, like so: ``` -export DEVITO_ARCH=clang +export DEVITO_ARCH=gcc ``` the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` - ### Using the environment If you are not already using a virtual environment, `poetry` will [create one for you by default](https://python-poetry.org/docs/basic-usage/#using-your-virtual-environment). You will need to use this virtual env when using or working on the package. diff --git a/docs/index.md b/docs/index.md index 6d96145a..c470f4ec 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,30 +14,46 @@ The initial release of NDK provides support for transcranial functional ultrasou ### Local installation -`neurotechdevkit` requires Python `>=3.9` and `<3.11` to be installed. You can find which Python version you have installed by running `python --version` in a terminal. If you don't have Python installed, or you are running an unsupported version, you can download it from [python.org](https://www.python.org/downloads/). Python environment managers like pyenv, conda, and poetry are all perfectly suitable as well. +`neurotechdevkit` requires Python `>=3.9` and `<3.11` to be installed. You can find which Python version you have installed by running `python --version` in a terminal. -You can install the package using: +If you don't have Python installed, or you are running an unsupported version, you can download it from [python.org](https://www.python.org/downloads/). Python environment managers like pyenv, conda, and poetry are all perfectly suitable as well. + +You can install the `neurotechdevkit` package using: ```bash pip install neurotechdevkit ``` -And then you must install stride using: +You also have to install stride, it can be done running: ```bash pip install git+https://github.com/trustimaging/stride ``` -`devito`, a dependency of `neurotechdevkit`, requires `libomp` to perform its runtime compilation. It can be installed on _MacOS_ with: +`devito`, a dependency of `neurotechdevkit`, requires `libomp`. On MacOS it can be installed with: ``` brew install libomp ``` +the output of the command above will look like this: + +``` +For compilers to find libomp you may need to set: +export LDFLAGS="-L/usr/local/opt/libomp/lib" +export CPPFLAGS="-I/usr/local/opt/libomp/include" +``` + +`devito` requires the directory with `libomp` headers to be accessible during the runtime compilation, you can make it accessible by exporting a new environment variable `CPATH` with the path for libomp headers, like so: + +``` +export CPATH="/usr/local/opt/libomp/include" +``` + You will also have to set an environment variable that defines what compiler `devito` will use, like so: ``` -export DEVITO_ARCH=clang +export DEVITO_ARCH=gcc ``` the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` From 230bb487fb439339efaeaf2c750fa68d5efae746 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Thu, 4 May 2023 12:31:09 +0000 Subject: [PATCH 021/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 17e7f3e9..b9e2dd45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.15" +version = "v0.0.16" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 1b77cab57132a544aed0dbb954b3443ce1b1fba2 Mon Sep 17 00:00:00 2001 From: Florin Pop Date: Thu, 4 May 2023 16:10:46 +0200 Subject: [PATCH 022/198] Redirect README to docs/index --- CONTRIBUTING.md | 1 + README.md | 97 +------------------------------------------- docs/contributing.md | 12 +----- docs/index.md | 16 ++++++-- 4 files changed, 17 insertions(+), 109 deletions(-) create mode 120000 CONTRIBUTING.md mode change 100644 => 120000 README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 120000 index 00000000..d0fcfe91 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +docs/contributing.md \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 57eb8a82..00000000 --- a/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Neurotech Development Kit (NDK) - -The _Neurotech Development Kit_ (NDK) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. -Featuring an easy-to-use API and pre-built examples, the NDK provides a seamless starting point for users. -Moreover, the NDK offers educational resources, such as interactive simulations and notebook-based tutorials, catering to a diverse audience including researchers, educators, engineers, and trainees. -By lowering the barrier of entry for newcomers and accelerating the progress of researchers, the NDK aims to be a versatile and invaluable tool for the neurotech community. - -The initial set of target users for the NDK are ultrasound simulation trainees – individuals with backgrounds in technical or neuroscience-related fields who are learning to perform ultrasound simulations. -Our goal is to help users familiarize themselves with ultrasound simulation, understand the importance of input parameters, and streamline the process of running and visualizing simulations. -In the future, we plan to expand the NDK's features to incorporate additional functionality and modalities, catering to a broader range of users, including ultrasound researchers, product developers, machine learning engineers, and many more. - -The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. -The Neurotech Development Kit is actively developed and we welcome feedback and contributions. - -## Exploring the Repository - -The NDK API was developed with easy-of-use in mind, and **3 lines of code is all you need to run a simulation**: - -```python -import neurotechdevkit as ndk - -scenario = ndk.make('scenario-2-2d-v0') -result = scenario.simulate_steady_state() -``` - -... and one line to see the results! - -```python -result.render_steady_state_amplitudes() -``` - -steady-state-results - -## Setup - -`neurotechdevkit` requires Python `>=3.9` and `<3.11` to be installed. You can find which Python version you have installed by running `python --version` in a terminal. - -If you don't have Python installed, or you are running an unsupported version, you can download it from [python.org](https://www.python.org/downloads/). Python environment managers like pyenv, conda, and poetry are all perfectly suitable as well. - -You can install the `neurotechdevkit` package using: - -```bash -pip install neurotechdevkit -``` - -You also have to install stride, it can be done running: - -```bash -pip install git+https://github.com/trustimaging/stride -``` - -`devito`, a dependency of `neurotechdevkit`, requires `libomp`. On MacOS it can be installed with: - -``` -brew install libomp -``` - -the output of the command above will look like this: - -``` -For compilers to find libomp you may need to set: -export LDFLAGS="-L/usr/local/opt/libomp/lib" -export CPPFLAGS="-I/usr/local/opt/libomp/include" -``` - -`devito` requires the directory with `libomp` headers to be accessible during the runtime compilation, you can make it accessible by exporting a new environment variable `CPATH` with the path for libomp headers, like so: - -``` -export CPATH="/usr/local/opt/libomp/include" -``` - -You will also have to set an environment variable that defines what compiler `devito` will use, like so: - -``` -export DEVITO_ARCH=gcc -``` - -the supported values for `DEVITO_ARCH` are: `'custom', 'gnu', 'gcc', 'clang', 'aomp', 'pgcc', 'pgi', 'nvc', 'nvc++', 'nvidia', 'cuda', 'osx', 'intel', 'icpc', 'icc', 'intel-knl', 'knl', 'dpcpp', 'gcc-4.9', 'gcc-5', 'gcc-6', 'gcc-7', 'gcc-8', 'gcc-9', 'gcc-10', 'gcc-11'` - -### Development - -See our [contribution requirements](docs/contributing.md) for more information on how to install the package locally using poetry and on how to contribute. - -A Makefile is provided to assist with common commands such as linting and running unit tests. - -```bash -$ make lint - -$ make test -``` - -See the Makefile for other commands. - -### Acknowledgements - -Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. diff --git a/README.md b/README.md new file mode 120000 index 00000000..e8923303 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +docs/index.md \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index 81c54c26..a269ddb2 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -122,17 +122,9 @@ Before opening a pull request, please make sure that all of the following requir ``` make lint ``` -1. spelling is checked: - ``` - make spellcheck - ``` -1. the documentation builds without warnings: - ``` - make docs - ``` 1. type hinting is used on all function and method parameters and return values, excluding tests 1. docstring usage conforms to the following: 1. all docstrings should follow [PEP257 Docstring Conventions](https://peps.python.org/pep-0257/) - 1. all public API classes, functions, methods, and properties have docstrings and follow the [Google Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings) - 1. docstrings on private objects are not required, but are encouraged where they would significantly aid understanding + 2. all public API classes, functions, methods, and properties have docstrings and follow the [Google Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings) + 3. docstrings on private objects are not required, but are encouraged where they would significantly aid understanding 1. testing is done using the pytest library, and test coverage should not unnecessarily decrease. diff --git a/docs/index.md b/docs/index.md index c470f4ec..9b9040c2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,16 @@ # Welcome to Neurotech Development Kit -The _Neurotech Development Kit_ (NDK) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. Featuring an easy-to-use API and pre-built examples, the NDK provides a seamless starting point for users. Moreover, the NDK offers educational resources, such as interactive simulations and notebook-based tutorials, catering to a diverse audience including researchers, educators, engineers, and trainees. By lowering the barrier of entry for newcomers and accelerating the progress of researchers, the NDK aims to be a versatile and invaluable tool for the neurotech community. +The _Neurotech Development Kit_ (NDK) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. +Featuring an easy-to-use API and pre-built examples, the NDK provides a seamless starting point for users. +Moreover, the NDK offers educational resources, such as interactive simulations and notebook-based tutorials, catering to a diverse audience including researchers, educators, engineers, and trainees. +By lowering the barrier of entry for newcomers and accelerating the progress of researchers, the NDK aims to be a versatile and invaluable tool for the neurotech community. -The initial set of target users for the NDK are ultrasound simulation trainees – individuals with backgrounds in technical or neuroscience-related fields who are learning to perform ultrasound simulations. Our goal is to help users familiarize themselves with ultrasound simulation, understand the importance of input parameters, and streamline the process of running and visualizing simulations. In the future, we plan to expand the NDK's features to incorporate additional functionality and modalities, catering to a broader range of users, including ultrasound researchers, product developers, machine learning engineers, and many more. +The initial set of target users for the NDK are ultrasound simulation trainees – individuals with backgrounds in technical or neuroscience-related fields who are learning to perform ultrasound simulations. +Our goal is to help users familiarize themselves with ultrasound simulation, understand the importance of input parameters, and streamline the process of running and visualizing simulations. +In the future, we plan to expand the NDK's features to incorporate additional functionality and modalities, catering to a broader range of users, including ultrasound researchers, product developers, machine learning engineers, and many more. -The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. The Neurotech Development Kit is actively developed and we welcome feedback and contributions. +The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. +The Neurotech Development Kit is actively developed and we welcome feedback and contributions.
![Simulation](images/ndk_example.gif){ width="900" } @@ -81,3 +87,7 @@ result.render_steady_state_amplitudes(show_material_outlines=False)
![Simulation](images/simulation_steady_state.png){ width="900" }
+ +### Acknowledgements + +Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. From 7ace9fad8d4c6d3b8d626261044c7c077930682d Mon Sep 17 00:00:00 2001 From: Florin Pop Date: Thu, 4 May 2023 16:19:44 +0200 Subject: [PATCH 023/198] Add circle config --- .circleci/config.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..8e161dc9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,36 @@ +# Tagging a commit with [circle front] will build the front page and perform tests-doc. +# Tagging a commit with [circle full] will build everything. +version: 2.1 + +jobs: + build_docs: + docker: + - image: cimg/base:current-22.04 + steps: + - checkout + + - run: + name: Install the latest version of Poetry + command: | + curl -sSL https://install.python-poetry.org | python3 - + + - run: + name: Install dependencies + command: | + poetry install --no-ansi + + - run: + name: Build HTML + command: | + make docs + + - store_artifacts: + path: site/ + destination: html + +workflows: + version: 2 + + default: + jobs: + - build_docs From 69ac085b33db0126ddcf3e754d0d91d2c713b4bf Mon Sep 17 00:00:00 2001 From: Florin Pop Date: Thu, 4 May 2023 16:24:30 +0200 Subject: [PATCH 024/198] Use python 3.10 image --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e161dc9..ac2b2a38 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build_docs: docker: - - image: cimg/base:current-22.04 + - image: cimg/python:3.10.10 steps: - checkout From 39d9ece69da7544a537fd6b6256ae6076a3e43f7 Mon Sep 17 00:00:00 2001 From: Florin Pop Date: Thu, 4 May 2023 16:27:18 +0200 Subject: [PATCH 025/198] Install stride --- .circleci/config.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac2b2a38..dc225235 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,6 +19,11 @@ jobs: command: | poetry install --no-ansi + - run: + name: Install stride + command: | + poetry run pip install git+https://github.com/trustimaging/stride + - run: name: Build HTML command: | From 4967245a63c06a2440e0bfa064bee22a942c58ee Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Thu, 4 May 2023 15:49:47 +0000 Subject: [PATCH 026/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b9e2dd45..46297847 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.16" +version = "v0.0.17" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From dd909c5bb3008e941f4b9f26a054b70afb9c81d3 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Thu, 4 May 2023 14:57:24 -0300 Subject: [PATCH 027/198] Replacing mkdocs markup with markdown markup --- docs/index.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9b9040c2..b62405f3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,9 +12,7 @@ In the future, we plan to expand the NDK's features to incorporate additional fu The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. The Neurotech Development Kit is actively developed and we welcome feedback and contributions. -
- ![Simulation](images/ndk_example.gif){ width="900" } -
+![Simulation](images/ndk_example.gif) ## Running @@ -84,9 +82,7 @@ result = scenario.simulate_steady_state() result.render_steady_state_amplitudes(show_material_outlines=False) ``` -
- ![Simulation](images/simulation_steady_state.png){ width="900" } -
+![Simulation](images/simulation_steady_state.png) ### Acknowledgements From b4886fb02dcb19fa1f80d366b1bdb882764a6eaf Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Thu, 4 May 2023 15:02:15 -0300 Subject: [PATCH 028/198] Using github external links to images so that we PyPi is able to render it --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index b62405f3..777fcf92 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ In the future, we plan to expand the NDK's features to incorporate additional fu The initial release of NDK provides support for transcranial functional ultrasound stimulation, with a focus on providing comprehensive documentation, API flexibility, and visualizations. The Neurotech Development Kit is actively developed and we welcome feedback and contributions. -![Simulation](images/ndk_example.gif) +![Simulation](https://raw.githubusercontent.com/agencyenterprise/neurotechdevkit/main/docs/images/ndk_example.gif) ## Running @@ -82,7 +82,7 @@ result = scenario.simulate_steady_state() result.render_steady_state_amplitudes(show_material_outlines=False) ``` -![Simulation](images/simulation_steady_state.png) +![Simulation](https://raw.githubusercontent.com/agencyenterprise/neurotechdevkit/main/docs/images/simulation_steady_state.png) ### Acknowledgements From acf770209b06192bd251702ec1af0dad79352446 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Thu, 4 May 2023 18:08:11 +0000 Subject: [PATCH 029/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 46297847..8567ffa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.17" +version = "v0.0.20" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 81f412603024968edfb4aa14a7023eba1810b1fd Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Mon, 8 May 2023 13:43:11 -0300 Subject: [PATCH 030/198] Adding codecov to CI --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85501640..9209a39f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,3 +60,7 @@ jobs: - name: Run tests run: | make test + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' From a9984441939c906e7aa480faf1803f22b8c693e1 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Fri, 5 May 2023 16:54:41 -0300 Subject: [PATCH 031/198] Adding codespell check --- Makefile | 2 ++ poetry.lock | 20 +++++++++++++++++++- pyproject.toml | 1 + src/neurotechdevkit/rendering/simulations.py | 2 +- src/neurotechdevkit/scenarios/_base.py | 16 ++++++++-------- src/neurotechdevkit/scenarios/_results.py | 2 +- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index edb06ab1..f7074cd6 100644 --- a/Makefile +++ b/Makefile @@ -8,12 +8,14 @@ lint: poetry run black src tests poetry run flake8 src tests poetry run mypy src + poetry run codespell src lint-check: poetry run isort --check src tests poetry run black --check src tests poetry run flake8 src tests poetry run mypy src + poetry run codespell src test: poetry run pytest tests diff --git a/poetry.lock b/poetry.lock index ce78e1a4..5f742b14 100644 --- a/poetry.lock +++ b/poetry.lock @@ -562,6 +562,24 @@ numpy = ">=1.6" pytools = ">=2015.1.2" six = "*" +[[package]] +name = "codespell" +version = "2.2.4" +description = "Codespell" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "codespell-2.2.4-py3-none-any.whl", hash = "sha256:7d984b8130108e6f82524b7d09f8b7bf2fb1e398c5d4b37d9e2bd310145b3e29"}, + {file = "codespell-2.2.4.tar.gz", hash = "sha256:0b4620473c257d9cde1ff8998b26b2bb209a35c2b7489f5dc3436024298ce83a"}, +] + +[package.extras] +dev = ["Pygments", "build", "chardet", "flake8", "flake8-pyproject", "pytest", "pytest-cov", "pytest-dependency", "tomli"] +hard-encoding-detection = ["chardet"] +toml = ["tomli"] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] + [[package]] name = "colorama" version = "0.4.6" @@ -4138,4 +4156,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "4057c5c78346f4f421580da855b3b2bbc8474b42e0d97eb6a5ec5939c28e1695" +content-hash = "9384a07b55da8e3426fe314e1fe72b8406195630986c85130295e031b59d6829" diff --git a/pyproject.toml b/pyproject.toml index 8567ffa5..587a25d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ pytest-asyncio = "^0.20.2" mkdocs-material = "^9.1.6" mkdocs-gallery = "^0.7.6" mkdocstrings = {extras = ["python"], version = "^0.21.2"} +codespell = "^2.2.4" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/src/neurotechdevkit/rendering/simulations.py b/src/neurotechdevkit/rendering/simulations.py index 349bea4b..cf329786 100644 --- a/src/neurotechdevkit/rendering/simulations.py +++ b/src/neurotechdevkit/rendering/simulations.py @@ -82,7 +82,7 @@ def create_pulsed_figure( the field along each axis. origin: An array of shape (2,) which contains the spatial coordinates (in meters) of the field element with indices (0, 0). - wavefield: An array in 2 spacial dimensions and one temporal dimension with the + wavefield: An array in 2 spatial dimensions and one temporal dimension with the recorded pressures during the simulation. The temporal dimension should be the last one. norm: The normalization method used to scale scalar data to the [0, 1] diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index d5f7728b..d08d7148 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -546,8 +546,8 @@ def _simulate_pulse( n_jobs: The number of threads to be used for the computation. Use None to leverage Devito automatic tuning. slice_axis: the axis along which to slice the 3D field to be recorded. If - None, then the complete field wil be recorded. Use 0 for X axis, 1 for Y - axis and 2 for Z axis. Only valid if `slice_position` is not None. + None, then the complete field will be recorded. Use 0 for X axis, 1 for + Y axis and 2 for Z axis. Only valid if `slice_position` is not None. slice_position: the position (in meters) along the slice axis at which the slice of the 3D field should be made. Only valid if `slice_axis` is not None. @@ -686,8 +686,8 @@ def _wavefield_slice( Args: slice_axis: the axis along which to slice the 3D field to be recorded. If - None, then the complete field wil be recorded. Use 0 for X axis, 1 for Y - axis and 2 for Z axis. + None, then the complete field will be recorded. Use 0 for X axis, 1 for + Y axis and 2 for Z axis. slice_position: the position (in meters) along the slice axis at which the slice of the 3D field should be made. @@ -741,8 +741,8 @@ def _validate_slice_args( Args: slice_axis: the axis along which to slice the 3D field to be recorded. If - None, then the complete field wil be recorded. Use 0 for X axis, 1 for Y - axis and 2 for Z axis. + None, then the complete field will be recorded. Use 0 for X axis, 1 + for Y axis and 2 for Z axis. slice_position: the position (in meters) along the slice axis at which the slice of the 3D field should be made. @@ -1024,8 +1024,8 @@ def simulate_pulse( n_jobs: The number of threads to be used for the computation. Use None to leverage Devito automatic tuning. slice_axis: the axis along which to slice the 3D field to be recorded. If - None, then the complete field wil be recorded. Use 0 for X axis, 1 for Y - axis and 2 for Z axis. Only valid if `slice_position` is not None. + None, then the complete field will be recorded. Use 0 for X axis, 1 for + Y axis and 2 for Z axis. Only valid if `slice_position` is not None. slice_position: the position (in meters) along the slice axis at which the slice of the 3D field should be made. Only valid if `slice_axis` is not None. diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index 7af83593..ac6ddee8 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -791,7 +791,7 @@ def _validate_slicing_options( Args: slice_axis: the axis along which to slice the 3D field to be recorded. If - None, then the complete field wil be recorded. + None, then the complete field will be recorded. slice_position: the position (in meters) along the slice axis at which the slice of the 3D field should be made. From 5c3b23e46f859644bf30684fd1983d3f0fd619a7 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Fri, 5 May 2023 17:24:34 -0300 Subject: [PATCH 032/198] Adding pycodestyle to project --- Makefile | 2 + poetry.lock | 32 +++++++++- pyproject.toml | 1 + src/neurotechdevkit/__init__.py | 9 +-- src/neurotechdevkit/rendering/__init__.py | 1 + src/neurotechdevkit/rendering/_animations.py | 9 ++- src/neurotechdevkit/rendering/_formatting.py | 8 +-- src/neurotechdevkit/rendering/_source.py | 10 +-- src/neurotechdevkit/rendering/_target.py | 6 +- src/neurotechdevkit/rendering/colormaps.py | 1 + src/neurotechdevkit/rendering/font.py | 1 + src/neurotechdevkit/rendering/layers.py | 8 ++- src/neurotechdevkit/rendering/layout.py | 7 +- src/neurotechdevkit/rendering/legends.py | 10 +-- src/neurotechdevkit/rendering/napari.py | 8 +-- src/neurotechdevkit/rendering/simulations.py | 10 +-- src/neurotechdevkit/scenarios/_base.py | 44 ++++++------- src/neurotechdevkit/scenarios/_resources.py | 12 ++-- src/neurotechdevkit/scenarios/_results.py | 39 ++++++------ src/neurotechdevkit/scenarios/_scenario_1.py | 2 +- src/neurotechdevkit/scenarios/_shots.py | 12 ++-- src/neurotechdevkit/scenarios/_time.py | 6 +- src/neurotechdevkit/scenarios/_utils.py | 23 ++++--- src/neurotechdevkit/sources.py | 67 ++++++++++---------- 24 files changed, 174 insertions(+), 154 deletions(-) diff --git a/Makefile b/Makefile index f7074cd6..6a9d0f5a 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ lint: poetry run flake8 src tests poetry run mypy src poetry run codespell src + poetry run pydocstyle src lint-check: poetry run isort --check src tests @@ -16,6 +17,7 @@ lint-check: poetry run flake8 src tests poetry run mypy src poetry run codespell src + poetry run pydocstyle src test: poetry run pytest tests diff --git a/poetry.lock b/poetry.lock index 5f742b14..b5668c52 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3039,6 +3039,24 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pydocstyle" +version = "6.3.0" +description = "Python docstring style checker" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + [[package]] name = "pyflakes" version = "2.5.0" @@ -3695,6 +3713,18 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -4156,4 +4186,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "9384a07b55da8e3426fe314e1fe72b8406195630986c85130295e031b59d6829" +content-hash = "e92bf8713fcb14b083b12c11f138ae8d95ce925505afe53f027919c49edbb946" diff --git a/pyproject.toml b/pyproject.toml index 587a25d7..5549e49d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ mkdocs-material = "^9.1.6" mkdocs-gallery = "^0.7.6" mkdocstrings = {extras = ["python"], version = "^0.21.2"} codespell = "^2.2.4" +pydocstyle = "^6.3.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index 1aba212a..34f32129 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -1,3 +1,4 @@ +"""Main package for the neurotechdevkit.""" from __future__ import annotations from . import scenarios @@ -12,13 +13,14 @@ class ScenarioNotFoundError(Exception): + """Exception raised when a scenario is not found.""" + pass -def make(scenario_id, complexity="fast"): +def make(scenario_id: str, complexity="fast"): """ - Initialize a scenario. This will return an object which represents - the simulation. + Initialize a scenario and return an object which represents the simulation. Args: scenario_id (str): the id of the scenario to load. Supported @@ -42,7 +44,6 @@ def make(scenario_id, complexity="fast"): Returns: scenarios.Scenario: an object representing the simulation. """ - if scenario_id not in _scenario_map: raise ScenarioNotFoundError( f"Scenario '{scenario_id}' does not exist. Please refer to documentation" diff --git a/src/neurotechdevkit/rendering/__init__.py b/src/neurotechdevkit/rendering/__init__.py index 68284238..3d98989d 100644 --- a/src/neurotechdevkit/rendering/__init__.py +++ b/src/neurotechdevkit/rendering/__init__.py @@ -1,3 +1,4 @@ +"""Rendering module.""" from ._animations import ( configure_matplotlib_for_embedded_animation, display_video_file, diff --git a/src/neurotechdevkit/rendering/_animations.py b/src/neurotechdevkit/rendering/_animations.py index 7b1e31e5..ce90cc03 100644 --- a/src/neurotechdevkit/rendering/_animations.py +++ b/src/neurotechdevkit/rendering/_animations.py @@ -12,7 +12,7 @@ def display_video_file(file_name: str) -> Video: - """Renders a video file in a Ipython environment. + """Render a video file in a Ipython environment. Args: file_name: the file name containing the animation to display. @@ -24,7 +24,7 @@ def display_video_file(file_name: str) -> Video: def video_only_output(func: Callable) -> Callable: - """A decorator to create the context for video creation. + """Create the context for video creation (decorator). It deactivates the interactive environment while the animation is being created. It re-activates the interactive environment after the animation was created. @@ -76,7 +76,7 @@ def make_animation( wavefield: npt.NDArray[np.float_], n_frames_undersampling: int, ) -> FuncAnimation: - """Creates an animation of a time evolution of `wavefield`. + """Create an animation of a time evolution of `wavefield`. Args: fig: matplotlib figure that would act as template for the animation. @@ -88,7 +88,6 @@ def make_animation( Returns: An animation object. """ - if wavefield.ndim != 3: raise ValueError("Animations only supported for 2D scenarios/slices only.") @@ -135,7 +134,7 @@ def save_animation( bitrate: int = 2500, ) -> None: """ - Saves an animation object to a file in disk. + Save an animation object to a file in disk. `ffmpeg` is required to create the animation. Currently only mp4 format is supported. diff --git a/src/neurotechdevkit/rendering/_formatting.py b/src/neurotechdevkit/rendering/_formatting.py index 97281027..737ea80f 100644 --- a/src/neurotechdevkit/rendering/_formatting.py +++ b/src/neurotechdevkit/rendering/_formatting.py @@ -6,7 +6,7 @@ def configure_title(fig: plt.Figure, title: str, x_pos: float = 0.5) -> None: - """Configures the title of the plot. + """Configure the title of the plot. Note: we might expect that 0.5 in figure coordinates would place the text centered above the plot, but this does not seem to always be the case. The `x_pos` parameter @@ -30,7 +30,7 @@ def configure_title(fig: plt.Figure, title: str, x_pos: float = 0.5) -> None: def configure_axis_labels( ax: plt.Axes, horizontal_label: str, vertical_label: str ) -> None: - """Configures the labels for the X and Y axes. + """Configure the labels for the X and Y axes. Args: ax: The axes containing the labels to be configured. @@ -50,7 +50,7 @@ def configure_axis_labels( def configure_axis_ticks(ax: plt.Axes) -> None: - """Configures the ticks and tick labels for the X and Y axes. + """Configure the ticks and tick labels for the X and Y axes. Args: ax: The axes containing the ticks to be configured. @@ -61,7 +61,7 @@ def configure_axis_ticks(ax: plt.Axes) -> None: def configure_grid(ax: plt.Axes) -> None: - """Configures the grid for the plot. + """Configure the grid for the plot. Args: ax: The axes containing the grid to be configured. diff --git a/src/neurotechdevkit/rendering/_source.py b/src/neurotechdevkit/rendering/_source.py index 5a4e9ed4..52e01ea5 100644 --- a/src/neurotechdevkit/rendering/_source.py +++ b/src/neurotechdevkit/rendering/_source.py @@ -133,7 +133,7 @@ def _load_most_similar_source_image( focal_length: float, source_is_flat: bool, ) -> npt.NDArray[np.float_]: - """Loads the source image which best matches the specified aperture and focus. + """Load the source image which best matches the specified aperture and focus. Args: aperture: The aperture of the source (in meters). @@ -161,7 +161,7 @@ def _load_most_similar_source_image( def source_should_be_flat(source: Source) -> bool: - """Determines if a source is flat based on the source type and focal length. + """Determine if a source is flat based on the source type and focal length. A source should be represented flat if the source is unfocused (as determined by `focal_length`) or the source is a phased array @@ -180,7 +180,7 @@ def source_should_be_flat(source: Source) -> bool: def _select_image_file( aperture: float, focal_length: float, source_is_flat: bool ) -> pathlib.Path: - """Selects the image file to load based on aperture and focal length. + """Select the image file to load based on aperture and focal length. For focused transducers, we select the image file based on the angle subtended. For planar transducers, there is only one option. @@ -205,7 +205,7 @@ def _select_image_file( def _choose_nearest(desired: float, options: list[int]) -> int: - """Returns the closest option to a desired value from a list of options. + """Return the closest option to a desired value from a list of options. Args: desired: The value for which we want the closest match. @@ -221,7 +221,7 @@ def _choose_nearest(desired: float, options: list[int]) -> int: def _translate_and_rotate( raw_img: npt.NDArray[np.float_], direction: npt.NDArray[np.float_] ) -> npt.NDArray[np.float_]: - """Applies the required transformations to a source image. + """Apply the required transformations to a source image. This function encapsulates three operations: 1. Pad the image array so that a rotation will not cut off parts of the image. diff --git a/src/neurotechdevkit/rendering/_target.py b/src/neurotechdevkit/rendering/_target.py index 3a7bebc1..408bb662 100644 --- a/src/neurotechdevkit/rendering/_target.py +++ b/src/neurotechdevkit/rendering/_target.py @@ -14,7 +14,7 @@ def create_target_drawing_artist( target_radius: float, transform: Transform, ) -> plt.Artist: - """Creates the matplotlib artist for the target symbol. + """Create the matplotlib artist for the target symbol. The caller is expected to provide the transform that takes the coordinates for the center and radius and map them into display coordinates. Eg. for placing the marker @@ -50,7 +50,7 @@ def create_target_legend_artist( target_radius: float, transform: Transform, ) -> plt.Artist: - """Creates the matplotlib artist for the target symbol. + """Create the matplotlib artist for the target symbol. The caller is expected to provide the transform that takes the coordinates for the center and radius and map them into display coordinates. Eg. for placing the marker @@ -81,7 +81,7 @@ def create_target_legend_artist( def _load_target(version: str) -> npt.NDArray[np.int_]: - """Loads a png of the target and returns it as a numpy array. + """Load a png of the target and returns it as a numpy array. The array is returned in RGBA channel order. diff --git a/src/neurotechdevkit/rendering/colormaps.py b/src/neurotechdevkit/rendering/colormaps.py index 0b047bf0..d2ba2aa8 100644 --- a/src/neurotechdevkit/rendering/colormaps.py +++ b/src/neurotechdevkit/rendering/colormaps.py @@ -1,3 +1,4 @@ +"""Colormaps available in neurotechdevkit.""" # New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, # and (in the case of viridis) Eric Firing. # diff --git a/src/neurotechdevkit/rendering/font.py b/src/neurotechdevkit/rendering/font.py index a88828e5..1e556caf 100644 --- a/src/neurotechdevkit/rendering/font.py +++ b/src/neurotechdevkit/rendering/font.py @@ -1,3 +1,4 @@ +"""Font definitions for the neurotechdevkit.""" import pathlib from matplotlib import font_manager diff --git a/src/neurotechdevkit/rendering/layers.py b/src/neurotechdevkit/rendering/layers.py index 9e4d4e45..e47b75e6 100644 --- a/src/neurotechdevkit/rendering/layers.py +++ b/src/neurotechdevkit/rendering/layers.py @@ -1,3 +1,4 @@ +"""Layers for rendering scenario figures.""" import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt @@ -22,7 +23,8 @@ def draw_source(ax: plt.Axes, source: SourceDrawingParams) -> None: def draw_target( ax: plt.Axes, target_loc: npt.NDArray[np.float_], target_radius: float ) -> None: - """Draw a layer showing the scenario target on top of a figure. + """ + Draw a layer showing the scenario target on top of a figure. This layer can be added to any scenario figure in 2D. @@ -31,8 +33,8 @@ def draw_target( target_loc: An array of shape (2,) indicating the location (in meters) of the target within the scenario. target_radius: The radius (in meters) of the target. - """ + """ target_artist = create_target_drawing_artist( target_loc, target_radius, transform=ax.transData ) @@ -96,7 +98,7 @@ def _upsample_field( def _get_outline_mask(field: npt.NDArray[np.int_]) -> npt.NDArray[np.bool_]: - """Returns a mask indicating where there is a transition between materials. + """Return a mask indicating where there is a transition between materials. Transitions are detected by comparing the input field by itself but shifted one pixel horizontally or vertically. Detected horizontal and vertical transitions diff --git a/src/neurotechdevkit/rendering/layout.py b/src/neurotechdevkit/rendering/layout.py index 1bea73fa..063b98ae 100644 --- a/src/neurotechdevkit/rendering/layout.py +++ b/src/neurotechdevkit/rendering/layout.py @@ -47,7 +47,6 @@ def create_layout_fig( fig: The new matplotlib figure. ax: The axes containing the plotted data. """ - assert len(extent) == 2, "The rendering only supports 2D fields." fig = plt.figure() @@ -77,7 +76,7 @@ def configure_layout_plot( horizontal_label: str = "Y", title: str = "Scenario Layout", ) -> None: - """Configures a layout plot figure including axes, title, and legend. + """Configure a layout plot figure including axes, title, and legend. Args: fig: The layout plot figure to configure. @@ -96,7 +95,6 @@ def configure_layout_plot( horizontal_label: The label to apply to the horizontal axis. title: The title to give the figure. """ - configure_title(fig, title) _configure_legend(ax, layer_labels, color_sequence, show_target, show_sources) configure_grid(ax) @@ -125,7 +123,6 @@ def _configure_legend( show_target: Whether or not to show the target marker. show_sources: Whether or not to show the source markers. """ - config = LegendConfig() for label, color in zip(layer_labels, color_sequence): @@ -151,7 +148,7 @@ def _configure_legend( def _get_material_layer_handle(color: str) -> plt.Line2D: - """Creates a legend handle for a material layer in the layout figure. + """Create a legend handle for a material layer in the layout figure. Args: color: the color to use for the layer in the legend. diff --git a/src/neurotechdevkit/rendering/legends.py b/src/neurotechdevkit/rendering/legends.py index d78a01a7..eebfa02e 100644 --- a/src/neurotechdevkit/rendering/legends.py +++ b/src/neurotechdevkit/rendering/legends.py @@ -35,7 +35,7 @@ def append_item( self._custom_handlers[type(handle)] = custom_handler def get_labels(self, title_case: bool = True) -> list[str]: - """Returns the list of configured labels. + """Return the list of configured labels. Args: title_case: If True, all labels will be converted to title case. If False, @@ -49,7 +49,7 @@ def get_labels(self, title_case: bool = True) -> list[str]: return self._labels.copy() def get_handles(self) -> list[object]: - """Returns the list of configured legend handles. + """Return the list of configured legend handles. Returns: The list of configured legend handles. @@ -57,7 +57,7 @@ def get_handles(self) -> list[object]: return self._handles.copy() def get_custom_handlers(self) -> dict[type, mpl.legend_handler.HandlerBase]: - """Returns a map containing custom legend handlers. + """Return a map containing custom legend handlers. Returns: A map from handle type to legend handler instance. @@ -90,7 +90,7 @@ def legend_artist( fontsize: float, handlebox: mpl.offsetbox.OffsetBox, ) -> plt.Artist: - """Returns the artist that draws the target in the legend. + """Return the artist that draws the target in the legend. Args: legend: The legend for which these legend artists are being created. @@ -148,7 +148,7 @@ def legend_artist( fontsize: float, handlebox: mpl.offsetbox.OffsetBox, ) -> tuple[plt.Artist, ...]: - """Returns the artist that draws the source in the legend. + """Return the artist that draws the source in the legend. Args: legend: The legend for which these legend artists are being created. diff --git a/src/neurotechdevkit/rendering/napari.py b/src/neurotechdevkit/rendering/napari.py index 4e253c18..ef1e46f8 100644 --- a/src/neurotechdevkit/rendering/napari.py +++ b/src/neurotechdevkit/rendering/napari.py @@ -144,7 +144,7 @@ def add_material_layers( scenario: "scenarios.Scenario3D", viewer_config: ViewerConfig3D, ) -> None: - """Adds the individual material layers as images to a napari Viewer. + """Add the individual material layers as images to a napari Viewer. Args: viewer: The napari Viewer to which the layers should be added. @@ -169,7 +169,7 @@ def add_material_layers( def add_steady_state_amplitudes( viewer: _NapariViewer, amplitudes: npt.NDArray[np.float_] ) -> None: - """Adds the steady-state amplitudes as an image layer to a napari Viewer. + """Add the steady-state amplitudes as an image layer to a napari Viewer. Args: viewer: The napari Viewer to which the target should be added. @@ -184,7 +184,7 @@ def add_steady_state_amplitudes( def add_target(viewer: _NapariViewer, scenario: "scenarios.Scenario3D") -> None: - """Adds the target as a shapes layer to a napari Viewer. + """Add the target as a shapes layer to a napari Viewer. Args: viewer: The napari Viewer to which the target should be added. @@ -241,7 +241,7 @@ def add_target(viewer: _NapariViewer, scenario: "scenarios.Scenario3D") -> None: def add_source( viewer: _NapariViewer, scenario: "scenarios.Scenario3D", source: "sources.Source" ) -> None: - """Adds the source as a points layer to a napari Viewer. + """Add the source as a points layer to a napari Viewer. Each individual point source is plotted as a point. diff --git a/src/neurotechdevkit/rendering/simulations.py b/src/neurotechdevkit/rendering/simulations.py index cf329786..86c2dcb1 100644 --- a/src/neurotechdevkit/rendering/simulations.py +++ b/src/neurotechdevkit/rendering/simulations.py @@ -46,7 +46,6 @@ def create_steady_state_figure( Returns: A tuple with the figure and the axis that contain the steady-state data plot. """ - assert len(extent) == 2, "The rendering only supports 2D fields." fig = plt.figure() @@ -66,7 +65,7 @@ def create_pulsed_figure( wavefield: npt.NDArray[np.float_], norm: str = "linear", ) -> tuple[plt.Figure, plt.Axes]: - """Creates a base figure containing the pulsed wavefield pressures. + """Create a base figure containing the pulsed wavefield pressures. The figure is used as a template to created frames for an animation. The colorbar is adjusted to the minimum and maximum pressure observed in the wavefield. @@ -120,7 +119,7 @@ def configure_result_plot( title: str, clim: tuple[float, float] | None = None, ) -> None: - """Configures a results plot figure. + """Configure a results plot figure. Configuration includes: axes, title, colorbar, and legend. @@ -139,7 +138,6 @@ def configure_result_plot( title: The title to give the figure. clim: A tuple with (min, max) values for the limits for the colorbar. """ - configure_title(fig, title, x_pos=0.63) _configure_colorbar(fig, ax, clim=clim) @@ -185,7 +183,6 @@ def _configure_legend(ax: plt.Axes, show_sources: bool, show_target: bool) -> No show_target: Whether or not to show the target marker. show_sources: Whether or not to show the source markers. """ - config = LegendConfig() if show_target: @@ -211,7 +208,7 @@ def _create_centered_bidirectional_cmap( vmin: float, vmax: float ) -> LinearSegmentedColormap: """ - Extends the default colormap (viridis) to support negative values. + Extend the default colormap (viridis) to support negative values. Values close to zero in absolute value have almost identical color. Values diverge as they approach the extremes values `vmin` and `vmax`. @@ -223,7 +220,6 @@ def _create_centered_bidirectional_cmap( Returns: A colormap where zero has always the same color (LinearSegmentedColorMap). """ - ratio = np.abs(vmin / vmax) n_points = 128 colors1 = cm.viridis(np.linspace(0, 1, int(n_points * ratio))) diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index d08d7148..dcca8943 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -52,7 +52,7 @@ class Target: class Scenario(abc.ABC): - """The base scenario""" + """The base scenario.""" _SCENARIO_ID: str _TARGET_OPTIONS: dict[str, Target] @@ -62,7 +62,7 @@ def __init__( origin: npt.NDArray[np.float_], complexity: str = "fast", ): - """Initializes the scenario""" + """Initialize the scenario""" self._complexity = complexity if self._complexity != "fast": raise ValueError("the only complexity currently supported is 'fast'") @@ -264,7 +264,7 @@ def _freeze_sources(self) -> None: self._sources.freeze() def get_layer_mask(self, layer_name: str) -> npt.NDArray[np.bool_]: - """Returns the mask for the desired layer. + """Return the mask for the desired layer. The mask is `True` at each gridpoint where the requested layer exists, and False elsewhere. @@ -288,7 +288,7 @@ def get_layer_mask(self, layer_name: str) -> npt.NDArray[np.bool_]: @abc.abstractmethod def get_target_mask(self) -> npt.NDArray[np.bool_]: - """Returns the mask for the target region. + """Return the mask for the target region. Returns: A boolean array indicating which gridpoints correspond to the target region. @@ -296,7 +296,7 @@ def get_target_mask(self) -> npt.NDArray[np.bool_]: pass def get_field_data(self, field: str) -> npt.NDArray[np.float_]: - """Returns the array of field values across the scenario for a particular field. + """Return the array of field values across the scenario for a particular field. Common fields include: @@ -318,11 +318,11 @@ def _compile_problem(self) -> stride.Problem: pass def reset(self) -> None: - """resets the scenario to initial state""" + """Reset the scenario to initial state.""" raise NotImplementedError() def add_source(self, source: Source) -> None: - """Adds the specified source to the scenario. + """Add the specified source to the scenario. Sources can also added or removed by modifying the Scenario.sources list. @@ -335,7 +335,7 @@ def add_source(self, source: Source) -> None: @abc.abstractmethod def get_default_source(self) -> Source: - """Creates and returns a default source for this scenario. + """Create and returns a default source for this scenario. Returns: The default source. @@ -343,7 +343,7 @@ def get_default_source(self) -> Source: pass def _ensure_source(self) -> None: - """Ensures the scenario includes at least one source. + """Ensure the scenario includes at least one source. If no source is pre-defined, the default source is included. """ @@ -400,7 +400,6 @@ def simulate_steady_state( Returns: An object containing the result of the steady-state simulation. """ - if center_frequency != 5.0e5: raise NotImplementedError( "500kHz is the only currently supported center frequency. Support for" @@ -558,7 +557,6 @@ def _simulate_pulse( Returns: An object containing the result of the pulsed simulation. """ - if center_frequency != 5.0e5: raise NotImplementedError( "500kHz is the only currently supported center frequency. Support for" @@ -615,7 +613,7 @@ def _simulate_pulse( def _setup_sub_problem( self, center_frequency: float, simulation_mode: str ) -> stride.SubProblem: - """Sets up a stride `SubProblem` for the simulation. + """Set up a stride `SubProblem` for the simulation. A SubProblem requires at least one source transducer. If no source is defined, a default source is used. @@ -638,7 +636,7 @@ def _setup_sub_problem( def _setup_shot( self, sources: list[Source], freq_hz: float, simulation_mode: str ) -> stride.Shot: - """Creates the stride `Shot` for the simulation. + """Create the stride `Shot` for the simulation. Args: sources: the source transducers to use within the shot. @@ -657,7 +655,7 @@ def _setup_shot( return create_shot(problem, sources, self.origin, wavelet, self.dx) def _create_pde(self) -> stride.Operator: - """Instantiates the stride `Operator` representing the PDE for the scenario. + """Instantiate the stride `Operator` representing the PDE for the scenario. All existing scenarios use the `IsoAcousticDevito` operator. @@ -674,7 +672,7 @@ def _create_pde(self) -> stride.Operator: def _wavefield_slice( self, slice_axis: int | None = None, slice_position: float | None = None ) -> tuple[slice, ...]: - """Defines the region of of the grid that should be recorded. + """Define the region of of the grid that should be recorded. The first element of the tuple is for time, while all remaining elements are for space and should match the dimensionality of space. @@ -734,7 +732,7 @@ def _wavefield_slice( def _validate_slice_args( self, slice_axis: int | None, slice_position: float | None ) -> None: - """Validates that slicing axis and position are within scenario range. + """Validate that slicing axis and position are within scenario range. `slice_axis` should be either 0, 1, or 2 (for X, Y, Z). `slice_position` must be within boundaries for `slice_axis` extent. @@ -780,7 +778,7 @@ def _validate_slice_args( def _get_steady_state_recording_time_bounds( self, ppp: int, n_cycles: int ) -> tuple[int, int]: - """Defines the indices bounding the period of time to be recorded. + """Define the indices bounding the period of time to be recorded. For steady-state simulations, we only want to keep the last few cycles of the simulation. @@ -800,7 +798,7 @@ def _get_steady_state_recording_time_bounds( return (time.num - n_frames, time.num - 1) def _get_pulsed_recording_time_bounds(self) -> tuple[int, int]: - """Defines the indices bounding the period of time to be recorded. + """Define the indices bounding the period of time to be recorded. For pulsed simulations, we want to keep the data from all timesteps. @@ -820,7 +818,7 @@ def _execute_pde( wavefield_slice: tuple[slice, ...], n_jobs: int | None = None, ) -> stride.Traces: - """Executes the PDE for the simulation. + """Execute the PDE for the simulation. Args: pde: The `Operator` containing the PDE to execute. @@ -888,7 +886,7 @@ def render_layout( show_target: bool = True, show_material_outlines: bool = False, ) -> None: - """Creates a matplotlib figure showing the 2D scenario layout. + """Create a matplotlib figure showing the 2D scenario layout. The grid can be turned on via: @@ -946,7 +944,7 @@ def render_layout( class Scenario3D(Scenario): @abc.abstractmethod def get_default_slice_axis(self) -> int: - """Returns the default slice_axis for this scenario. + """Return the default slice_axis for this scenario. This field is used if the slice_axis is not specified when plotting 3D data in 2D. @@ -958,7 +956,7 @@ def get_default_slice_axis(self) -> int: @abc.abstractmethod def get_default_slice_position(self, axis: int) -> float: - """Returns the default slice_position (in meters) for this scenario. + """Return the default slice_position (in meters) for this scenario. This field is used if the slice_position is not specified when plotting 3D data in 2D. @@ -1054,7 +1052,7 @@ def render_layout( show_target: bool = True, show_material_outlines: bool = False, ) -> None: - """Creates a matplotlib figure showing a 2D slice of the scenario layout. + """Create a matplotlib figure showing a 2D slice of the scenario layout. In order to visualize the 3D scenario in a 2D plot, a slice through the scenario needs to be specified via `slice_axis` and `slice_position`. Eg. to take a slice diff --git a/src/neurotechdevkit/scenarios/_resources.py b/src/neurotechdevkit/scenarios/_resources.py index 9e769250..4e1a713e 100644 --- a/src/neurotechdevkit/scenarios/_resources.py +++ b/src/neurotechdevkit/scenarios/_resources.py @@ -7,7 +7,7 @@ def get_available_ram_memory() -> float: - """Returns the available RAM memory in GB. + """Return the available RAM memory in GB. Returns: The available RAM memory in GB. @@ -17,7 +17,7 @@ def get_available_ram_memory() -> float: def get_available_cpus() -> int: - """Returns the available CPUs. + """Return the available CPUs. Returns: The available number of CPUs. @@ -34,7 +34,7 @@ def estimate_memory_required( n_cycles_steady_state: int, time_steps: int, ) -> int: - """Estimates the RAM memory required in GB to run a steady state simulation. + """Estimate the RAM memory required in GB to run a steady state simulation. A linear model is used to estimate the memory required. The coefficients of the model were determined by least squares regression on benchmark simulations. @@ -51,7 +51,6 @@ def estimate_memory_required( Returns: The estimate of RAM (in GB) required to run the simulation. """ - intercept = 7.400 coefs = np.array([1.49829208e-10, -3.29276854e00, 1.38685890e00]) vals = np.array( @@ -68,7 +67,7 @@ def estimate_running_time( time_steps: int, n_threads: int, ) -> float: - """Estimates the time (in seconds) to complete the simulation. + """Estimate the time (in seconds) to complete the simulation. Computation time is estimated from a linear model. Which linear model to select is determined by the number of CPU/threads available. In the case that number of @@ -122,7 +121,7 @@ def budget_time_and_memory_resources( ram_available_gb: float = get_available_ram_memory(), ) -> None: """ - Informs the user of the time and memory resources needed to complete the simulation. + Inform the user of the time and memory resources needed to complete the simulation. The default value for n_threads assumes that all CPUs in the computer are used. The default value for ram_available_gb assumes that all RAM memory in the computer @@ -143,7 +142,6 @@ def budget_time_and_memory_resources( ram_available_gb: the RAM memory available for the simulation (default is all available RAM memory). """ - n_points = int(np.prod(grid_shape)) # Memory estimation diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index ac6ddee8..d9c837fc 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -47,7 +47,7 @@ class Result(abc.ABC): traces: stride.Traces def save_to_disk(self, filepath: str | pathlib.Path) -> None: - """Saves the result to disk to a gzip compressed file. + """Save the result to disk to a gzip compressed file. The gzip compressed file is a pickle object. @@ -74,7 +74,7 @@ def save_to_disk(self, filepath: str | pathlib.Path) -> None: @abc.abstractmethod def _generate_save_data(self) -> dict: - """Collects objects to be saved to disk for simulation results. + """Collect objects to be saved to disk for simulation results. The result data saved to disk will depend on the type of simulation. Currently `PulsedResults` and `SteadyStateResults` results. @@ -112,7 +112,7 @@ class SteadyStateResult(Result): def _extract_steady_state( self, by_slice: bool | None = None ) -> npt.NDArray[np.float_]: - """Extracts the steady state results from the simulation wavefield. + """Extract the steady state results from the simulation wavefield. Args: by_slice: If False, the fft is executed over the entire wavefield array at @@ -137,7 +137,7 @@ def _extract_steady_state( ) def get_steady_state(self) -> npt.NDArray[np.float_]: - """Returns the steady-state array and while computing it if necessary. + """Return the steady-state array and while computing it if necessary. Returns: An array containing steady-state pressure wave amplitudes (in pascals). @@ -160,7 +160,7 @@ def metrics(self) -> dict[str, dict[str, str | float]]: return metrics.calculate_all_metrics(self) def _generate_save_data(self) -> dict: - """Collects objects to be saved to disk for steady-state simulation results. + """Collect objects to be saved to disk for steady-state simulation results. Returns: A dictionary with the objects to be saved to disk. @@ -391,7 +391,7 @@ def render_steady_state_amplitudes_3d( def _extract_steady_state_amplitude( data: npt.NDArray[np.float_], freq_hz: float, dt: float, by_slice: bool ) -> npt.NDArray[np.float_]: - """Extracts the amplitude of steady-state waves using an FFT. + """Extract the amplitude of steady-state waves using an FFT. Note: in order to get the best results, dt should fit evenly into one cycle and we need to integrate over integer number of cycles. @@ -408,7 +408,6 @@ def _extract_steady_state_amplitude( Returns: The steady-state wave amplitudes over the spatial dimensions (in pascals). """ - freqs = np.fft.fftfreq(data.shape[-1], d=dt) freq_idx = np.argwhere(np.abs(freqs - freq_hz) < 1e-5).item() scaling = data.shape[-1] / 2 @@ -455,7 +454,7 @@ class PulsedResult(Result): recorded_slice: tuple[int, float] | None = None def _recording_times(self) -> npt.NDArray[np.float_]: - """Computes the time (in seconds) for each recorded frame in the wavefield. + """Compute the time (in seconds) for each recorded frame in the wavefield. Returns: A 1D array with the time in seconds for each step. @@ -468,7 +467,7 @@ def _recording_times(self) -> npt.NDArray[np.float_]: return times def _validate_time_lim(self, time_lim: tuple[np.float_, np.float_]) -> None: - """Validates the input time limit for the animation. + """Validate the input time limit for the animation. Args: time_lim: the input time limit tuple to validate. The expected format is @@ -487,7 +486,7 @@ def _validate_time_lim(self, time_lim: tuple[np.float_, np.float_]) -> None: @staticmethod def _check_ffmpeg_is_installed() -> None: - """Checks that ffmpeg command is available. + """Check that ffmpeg command is available. It is required to save the animations to disk. @@ -528,7 +527,7 @@ def _validate_file_name(file_name, overwrite) -> None: ) def _generate_save_data(self) -> dict: - """Collects objects to be saved to disk for pulsed simulation results. + """Collect objects to be saved to disk for pulsed simulation results. Returns: A dictionary with the objects to be saved to disk. @@ -573,7 +572,7 @@ def render_pulsed_simulation_animation( time_lim: tuple[np.float_, np.float_] | None = None, norm: str = "linear", ) -> FuncAnimation: - """Creates a matplotlib animation with the time evolution of the wavefield. + """Create a matplotlib animation with the time evolution of the wavefield. The created animation will be displayed as an interactive widget in a IPython or Jupyter Notebook environment. @@ -621,7 +620,7 @@ def create_video_file( bitrate: int = 2500, overwrite: bool = False, ) -> None: - """Saves a `mp4` animation file to disk with the results of the simulation. + """Save a `mp4` animation file to disk with the results of the simulation. Currently only mp4 format supported. `ffmpeg` command line tools needs to be installed. @@ -695,7 +694,6 @@ def _build_animation( Returns: A matplotlib animation object. """ - extent = self.scenario.extent origin = self.scenario.origin wavefield = self.wavefield @@ -783,7 +781,7 @@ class PulsedResult3D(PulsedResult): def _validate_slicing_options( self, slice_axis: int | None, slice_position: float | None ) -> None: - """Checks that the slicing arguments are consistent with the recorded field. + """Check that the slicing arguments are consistent with the recorded field. Only one slicing of the field is permitted, either at recording time or at rendering time. If the user recorded only a slice of the 3D field, slicing @@ -818,7 +816,7 @@ def render_pulsed_simulation_animation( time_lim: tuple[np.float_, np.float_] | None = None, norm: str = "linear", ) -> FuncAnimation: - """Creates a matplotlib animation with the time evolution of the wavefield. + """Create a matplotlib animation with the time evolution of the wavefield. The created animation will be displayed as an interactive widget in a IPython or Jupyter Notebook environment. @@ -846,7 +844,6 @@ def render_pulsed_simulation_animation( Returns: An matplotlib animation object. """ - animation = self._build_animation( show_sources=show_sources, show_target=show_target, @@ -876,7 +873,7 @@ def create_video_file( bitrate: int = 2500, overwrite: bool = False, ) -> None: - """Saves a `mp4` animation file to disk with the results of the simulation. + """Save a `mp4` animation file to disk with the results of the simulation. Currently only mp4 format supported. `ffmpeg` command line tools needs to be installed. @@ -1068,7 +1065,7 @@ def create_steady_state_result( wavefield: npt.NDArray[np.float_], traces: stride.Traces, ) -> SteadyStateResult: - """Utility function for creating a steady state result. + """Create a steady state result. Creates a SteadyStateResult2D or SteadyStateResult3D depending on the number of wavefield spatial dimensions. If the ndim of the wavefield is N, then the wavefield @@ -1129,7 +1126,7 @@ def create_pulsed_result( traces: stride.Traces, recorded_slice: tuple[int, float] | None = None, ) -> PulsedResult: - """Utility function for creating results from pulsed simulations. + """Create results from pulsed simulations. Creates a PulsedResult2D or PulsedResult3D depending on the number of wavefield spatial dimensions. If the ndim of the wavefield is N, then the wavefield has N-1 @@ -1181,7 +1178,7 @@ def create_pulsed_result( def load_result_from_disk(filepath: str | pathlib.Path) -> Result: - """Loads a result from disk from a gzip compressed pickle file. + """Load a result from disk from a gzip compressed pickle file. !!! warning This functionality is experimental, so do do not be surprised if you diff --git a/src/neurotechdevkit/scenarios/_scenario_1.py b/src/neurotechdevkit/scenarios/_scenario_1.py index 9741d687..0a67d841 100644 --- a/src/neurotechdevkit/scenarios/_scenario_1.py +++ b/src/neurotechdevkit/scenarios/_scenario_1.py @@ -14,7 +14,7 @@ class _Scenario1MixinProtocol(Protocol): - """Provide type-hinting for Scenario 1 members used by mixins""" + """Provide type-hinting for Scenario 1 members used by mixins.""" @property def scenario_id(self) -> str: diff --git a/src/neurotechdevkit/scenarios/_shots.py b/src/neurotechdevkit/scenarios/_shots.py index 9ee5a717..f65cfdbd 100644 --- a/src/neurotechdevkit/scenarios/_shots.py +++ b/src/neurotechdevkit/scenarios/_shots.py @@ -15,7 +15,7 @@ def create_shot( wavelet: npt.NDArray[np.float_], dx: float, ) -> stride.Shot: - """Compiles and returns a shot for a problem. + """Compile and return a shot for a problem. This function will add the point sources for each source to the problem geometry, build the shot object, build the wavelet array for the shot, and then add the @@ -61,7 +61,7 @@ def _add_sources_to_geometry( sources: list[sources.Source], origin: npt.NDArray[np.float_], ) -> list[TransducerLocation]: - """Adds and returns source transducers at locations specified by all sources. + """Add and return source transducers at locations specified by all sources. Every point sources from all sources is added to the problem geometry. @@ -90,7 +90,7 @@ def _add_sources_to_geometry( def _add_points_for_source_to_geometry( problem: stride.Problem, coords: npt.NDArray[np.float_] ) -> list[TransducerLocation]: - """Adds and returns source transducers at locations specified by coords. + """Add and return source transducers at locations specified by coords. Each point source in a source is added to the stride problem geometry, and then the TransducerLocation for each point source is returned so that it can be added to the @@ -124,7 +124,7 @@ def _build_shot_wavelets_array( dx: float, dt: float, ) -> npt.NDArray[np.float_]: - """Returns the scaled and delayed wavelet array for all sources. + """Return the scaled and delayed wavelet array for all sources. The scaling and delays are determined by each individual source. @@ -153,7 +153,7 @@ def _build_shot_wavelets_array( def _get_wavelets_for_source( wavelet: npt.NDArray[np.float_], source: sources.Source, dx: float, dt: float ) -> npt.NDArray[np.float_]: - """Returns the scaled and delayed wavelet array for a single source. + """Return the scaled and delayed wavelet array for a single source. Args: wavelet: a 1D array with shape (num_time_steps,) containing the pressure @@ -177,7 +177,7 @@ def _create_delayed_source_wavelets( wavelet: npt.NDArray[np.float_], delays: npt.NDArray[np.float_], dt: float ) -> npt.NDArray[np.float_]: """ - Applies time delays to a provided source wavelet for each point source. + Apply time delays to a provided source wavelet for each point source. Args: wavelet: a 1D array with shape (num_time_steps,) containing the pressure diff --git a/src/neurotechdevkit/scenarios/_time.py b/src/neurotechdevkit/scenarios/_time.py index 34160462..f976c7de 100644 --- a/src/neurotechdevkit/scenarios/_time.py +++ b/src/neurotechdevkit/scenarios/_time.py @@ -17,7 +17,7 @@ def select_simulation_time_for_steady_state( n_cycles_steady_state: int, delay: float, ) -> stride.Time: - """Determines how much time (in seconds) to simulate for a steady-state simulation. + """Determine how much time (in seconds) to simulate for a steady-state simulation. In order to reach near-steady-state conditions, we need the simulation to run for enough time for the waves to fully propagate to all edges of the simulation and @@ -58,7 +58,7 @@ def select_simulation_time_for_pulsed( materials: Mapping[str, Struct], delay: float, ): - """Determines how much time (in seconds) to simulate for a pulsed simulation. + """Determine how much time (in seconds) to simulate for a pulsed simulation. For pulsed simulations, we usually want to simulate enough time for the wave to fully propagate to the edges of the scenario, but we don't need to worry about @@ -101,7 +101,7 @@ def create_time_grid(*, freq_hz: float, ppp: int, sim_time: float) -> stride.Tim def find_largest_delay_in_sources(sources: FrozenList) -> float: - """Finds the largest delay (in seconds) among all sources + """Find the largest delay (in seconds) among all sources. Args: sources: a list with all sources in a scenario. diff --git a/src/neurotechdevkit/scenarios/_utils.py b/src/neurotechdevkit/scenarios/_utils.py index ba18151b..7394fcf6 100644 --- a/src/neurotechdevkit/scenarios/_utils.py +++ b/src/neurotechdevkit/scenarios/_utils.py @@ -15,7 +15,7 @@ def make_grid( extra: int | Iterable[int] = 50, absorbing: int | Iterable[int] = 40, ) -> stride.Grid: - """Creates a stride Grid. + """Create a stride Grid. Note that the time component of the grid is not defined here. That is created at simulation time because it depends on simulation parameters. @@ -63,14 +63,13 @@ def add_material_fields_to_problem( layer_ids: Mapping[str, int], masks: Mapping[str, npt.NDArray[np.bool_]], ) -> stride.Problem: - """Adds material fields as media to the problem. + """Add material fields as media to the problem. Included fields are: * the speed of sound (in m/s) * density (in kg/m^3) * absorption (in dB/cm) """ - grid = problem.grid vp = stride.ScalarField(name="vp", grid=grid) # [m/s] @@ -99,7 +98,7 @@ def add_material_fields_to_problem( def choose_wavelet_for_mode(simulation_mode: str) -> str: - """Returns the appropriate wavelet name for a given simulation mode. + """Return the appropriate wavelet name for a given simulation mode. Args: simulation_mode: the type of simulation which will be run. @@ -124,7 +123,7 @@ def wavelet_helper( freq_hz: float = 5.0e5, pressure: float = 6.0e4, ) -> npt.NDArray[np.float_]: - """Creates an array corresponding to the requested wavelet. + """Create an array corresponding to the requested wavelet. The wavelet returned from this function is intended to be applied to stride point sources. It should be scaled and delayed as needed in order to simulate a source @@ -169,7 +168,7 @@ def wavelet_helper( def slice_field( field: npt.NDArray, scenario, slice_axis: int, slice_position: float ) -> npt.NDArray: - """Returns a slice of a field at a desired position along an axis. + """Return a slice of a field at a desired position along an axis. If `slice_position` does not align exactly with the scenario grid, the closest gridpoint will be used. If `slice_position` is outside of the bounds of the grid, @@ -196,7 +195,7 @@ def slice_field( def drop_element(arr: npt.NDArray, drop_idx: int) -> npt.NDArray: - """Drops the element of a vector which corresponds to slice_axis. + """Drop the element of a vector which corresponds to slice_axis. Args: arr: a 1D numpy array containing the vector to be sliced. @@ -211,7 +210,7 @@ def drop_element(arr: npt.NDArray, drop_idx: int) -> npt.NDArray: def drop_column(arr: npt.NDArray, drop_idx: int) -> npt.NDArray: - """Drops the column of a 2D array which corresponds to slice_axis. + """Drop the column of a 2D array which corresponds to slice_axis. Args: arr: a 2D numpy array containing the data to be sliced. @@ -232,7 +231,7 @@ def create_grid_elliptical_mask( a: float, b: float, ) -> npt.NDArray[np.bool_]: - """Returns a 2D mask array for an ellipse with the specified parameters. + """Return a 2D mask array for an ellipse with the specified parameters. Array elements are True for the gridpoints within the ellipse and False otherwise. @@ -256,7 +255,7 @@ def create_grid_circular_mask( center: npt.NDArray[np.float_], radius: float, ) -> npt.NDArray[np.bool_]: - """Returns a 2D mask array for a circle with the specified parameters. + """Return a 2D mask array for a circle with the specified parameters. Array elements are True for the gridpoints within the circle and False otherwise. @@ -279,7 +278,7 @@ def create_grid_spherical_mask( center: npt.NDArray[np.float_], radius: float, ) -> npt.NDArray[np.bool_]: - """Returns a 3D mask array for a sphere with the specified parameters. + """Return a 3D mask array for a sphere with the specified parameters. Array elements are True for the gridpoints within the sphere and False otherwise. @@ -302,7 +301,7 @@ def _create_nd_ellipse_mask( center: npt.NDArray[np.float_], radii: npt.NDArray[np.float_], ) -> npt.NDArray[np.bool_]: - """Returns a mask array for an N-D ellipse with the specified parameters. + """Return a mask array for an N-D ellipse with the specified parameters. If the grid is 2D, then a 2D ellipse will be returned. If the grid is 3D, then a 3D ellipsoid will be returned. diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index b615ed96..d67c8a12 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -97,8 +97,9 @@ def num_points(self) -> int: @property def delay(self) -> float: - """The delay (in seconds) for the source as a whole. `delay` should be - non-negative. + """The delay (in seconds) for the source as a whole. + + `delay` should be non-negative. """ return self._delay @@ -108,7 +109,7 @@ def point_source_delays(self) -> npt.NDArray[np.float_]: return np.full(shape=(self.num_points,), fill_value=self.delay) def _validate_delay(self, delay: float) -> None: - """Validates that `delay` is non-negative` + """Validate that `delay` is non-negative. Args: delay (float, optional): the delay (in seconds) that the source needs @@ -122,7 +123,7 @@ def _validate_delay(self, delay: float) -> None: @abc.abstractmethod def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. This method must be implemented by all concrete Source classes. @@ -134,7 +135,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: @abc.abstractmethod def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale depends on the relative density of source points vs grid points. @@ -187,7 +188,7 @@ def _angle_range(self) -> float: return 2 * np.arcsin(self.aperture / (2 * self.focal_length)) def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. This method works by calculating points uniformly spread along an arc of a circle. @@ -220,7 +221,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: return coords def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale is equal to the ratio between the density of grid points along a line and the density of source points along the arc. @@ -249,7 +250,7 @@ class FocusedSource3D(Source): """ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. This method works by calculating points along a section of a spherical shell. It is built on top of the stride function `geometries.ellipsoidal` but uses @@ -284,7 +285,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: ) def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale is equal to the ratio between the density of grid points in a plane and the density of source points along the bowl surface. @@ -306,7 +307,7 @@ def calculate_waveform_scale(self, dx: float) -> float: def _calculate_source_density( aperture: float, radius: float, num_points: int ) -> float: - """Calculates the source point density (in points / meter^2). + """Calculate the source point density (in points / meter^2). The density is considered based on the number of source points divided by the surface area of the bowl. @@ -326,7 +327,7 @@ def _calculate_source_density( @staticmethod def _calculate_threshold(aperture: float, radius: float) -> float: - """Calculates the threshold value to pass to Stride's + """Calculate the threshold value to pass to Stride's `geometries.ellipsoidal` utility function. The `threshold` is used by Stride function `geometries.ellipsoidal` and is a @@ -353,7 +354,7 @@ def _calculate_threshold(aperture: float, radius: float) -> float: def _calculate_rotation_parameters( unit_direction: npt.NDArray[np.float_], ) -> tuple[npt.NDArray[np.float_], float]: - """Calculates the rotational parameters to pass to Stride's + """Calculate the rotational parameters to pass to Stride's `geometries.ellipsoidal` utility function. The bowl is originally created with the axis of symmetry along the z-axis, and @@ -430,7 +431,7 @@ class PlanarSource2D(UnfocusedMixin, Source): """ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. This method works by calculating points uniformly spread along the line segment. @@ -446,7 +447,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: return self.position + unit_line * np.expand_dims(line_parametrization, 1) def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale is equal to the ratio between the density of grid points along a line and the density of source points along the line segment source. @@ -471,7 +472,7 @@ class PlanarSource3D(UnfocusedMixin, Source): """ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. This method works by calculating points along a disk. It is built on top of the stride function `geometries.disk`, which returns points distributed along a @@ -490,7 +491,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: ) def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale is equal to the ratio between the density of grid points along a plane and the density of source points along the disk source. @@ -508,7 +509,7 @@ def calculate_waveform_scale(self, dx: float) -> float: class _PhasedArrayMixinProtocol(Protocol): - """Provide type-hinting for PhasedMixin""" + """Provide type-hinting for PhasedMixin.""" _element_delays: npt.NDArray[np.float_] @@ -715,8 +716,7 @@ def point_source_delays(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float @property def element_positions(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float_]: - """An array with the position of the center of each element of the array""" - + """An array with the position of the center of each element of the array""" positions = np.zeros(shape=(self.num_elements, len(self.position))) point_mapping = self.point_mapping coords = self.coordinates @@ -760,7 +760,7 @@ def _validate_input_configuration( @staticmethod def _validate_num_elements(num_elements: int) -> None: - """Ensures that the number of elements is positive, greater than 1. + """Ensure that the number of elements is positive, greater than 1. Currently phased arrays with one element are not supported. @@ -777,7 +777,7 @@ def _validate_num_elements(num_elements: int) -> None: @staticmethod def _validate_element_delays(element_delays, num_elements) -> None: - """Checks that the input value for `element_delays` meets the requirements. + """Check that the input value for `element_delays` meets the requirements. If `element_delays` is None, no check is performed If `element_delays` is not None, it must be a 1D array with length equal to @@ -792,7 +792,6 @@ def _validate_element_delays(element_delays, num_elements) -> None: Raises: ValueError if `element_delays` is invalid. """ - if element_delays is None: return element_delays = np.array(element_delays) @@ -831,7 +830,7 @@ def _calculate_line_boundaries( self, ) -> tuple[npt.NDArray[np.float_], npt.NDArray[np.float_]]: """ - Calculates the start and end of each line element in 1D. + Calculate the start and end of each line element in 1D. Returns: A tuple with two arrays for the minimum and maximum position. @@ -849,7 +848,7 @@ def _distribute_points_in_elements( num_elements: int, num_points: int ) -> tuple[slice, ...]: """ - Distributes `num_points` evenly among a specified number of elements. + Distribute `num_points` evenly among a specified number of elements. Computes an array indicating the start and end indices of points assigned to each element. @@ -879,7 +878,7 @@ def _translate( coords: npt.NDArray[np.float_], position: npt.NDArray[np.float_] ) -> npt.NDArray[np.float_]: """ - Translates the source to place the center at `position`. + Translate the source to place the center at `position`. Args: coords: array with the coordinates to translate. @@ -897,7 +896,7 @@ def _translate( def _broadcast_delays( self: _PhasedArrayMixinProtocol, delays: npt.NDArray[np.float_] ) -> npt.NDArray[np.float_]: - """Translates the delays per element into delays per source point. + """Translate the delays per element into delays per source point. All source points within one element have the same delay. @@ -919,7 +918,7 @@ def txdelay( speed: float = 1500, # m/s speed of sound in water ) -> float: """ - Computes the delay (in seconds) required to tilt the wavefront. + Compute the delay (in seconds) required to tilt the wavefront. The delays from element n to element n+1 to achieve a wavefront with `tilt_angle` respect to the normal. Positive angles lead to counter-clockwise @@ -967,7 +966,6 @@ def _calculate_focus_tilt_element_delays( Returns: An array with the negative delay (in seconds) per array element. """ - delays = np.zeros(shape=(self.num_elements,)) distances = np.array( [np.linalg.norm(ec - self.focal_point) for ec in self.element_positions], @@ -1072,7 +1070,7 @@ def _rotate( return r_coords def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. This method works by calculating points uniformly spread along the line of each segment. When `num_points` can not be evenly distributed in @@ -1098,7 +1096,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: return coords def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale is equal to the ratio between the density of grid points along a line and the density of source points along the line segment source. @@ -1232,7 +1230,7 @@ def focal_point(self) -> npt.NDArray[np.float_]: def _validate_center_line( center_line: npt.NDArray[np.float_], direction: npt.NDArray[np.float_] ) -> npt.NDArray[np.float_]: - """Ensures that the center line input parameter is a valid one. + """Ensure that the center line input parameter is a valid one. Center line must not be parallel to the direction. Center line must be perpendicular to the direction. @@ -1305,7 +1303,6 @@ def _distribute_points_within_element( along the diagonal and anti-diagonal axes of the rectangle are generated and added to the existing grid of points. """ - # Calculate the number of rows and columns in the grid n_rows = int(np.sqrt(n_points)) n_cols = int(np.floor(n_points / n_rows)) @@ -1434,7 +1431,7 @@ def _align_to_unit_center_line( return oriented_coords def _calculate_coordinates(self) -> npt.NDArray[np.float_]: - """Calculates the coordinates of the point source cloud for the source. + """Calculate the coordinates of the point source cloud for the source. Returns: An array containing the coordinates (in meters) of the point sources that @@ -1461,7 +1458,7 @@ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: return coords def calculate_waveform_scale(self, dx: float) -> float: - """Calculates the scale factor to apply to waveforms from this source. + """Calculate the scale factor to apply to waveforms from this source. The scale is equal to the ratio between the density of grid points along a plane and the density of source points along the planar source. @@ -1527,7 +1524,7 @@ def _rotate_3d( def _unit_vector(vector: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]: - """Returns the unit vector that is parallel to the input vector. + """Return the unit vector that is parallel to the input vector. Args: vector: a 1D numpy array. From 24826ddb6ac2abc2602fa6906934d860942db6e6 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Mon, 8 May 2023 08:16:54 -0300 Subject: [PATCH 033/198] Fixing docstrings --- src/neurotechdevkit/__init__.py | 2 +- src/neurotechdevkit/rendering/layers.py | 3 +- src/neurotechdevkit/rendering/layout.py | 1 + src/neurotechdevkit/rendering/legends.py | 2 ++ src/neurotechdevkit/rendering/napari.py | 1 + src/neurotechdevkit/rendering/simulations.py | 1 + src/neurotechdevkit/scenarios/__init__.py | 1 + src/neurotechdevkit/scenarios/_base.py | 20 +++++++----- src/neurotechdevkit/scenarios/_metrics.py | 12 +++++--- src/neurotechdevkit/scenarios/_results.py | 3 -- src/neurotechdevkit/scenarios/_scenario_0.py | 5 +++ src/neurotechdevkit/scenarios/_scenario_1.py | 9 ++++++ src/neurotechdevkit/scenarios/_scenario_2.py | 11 ++++++- src/neurotechdevkit/scenarios/_utils.py | 1 + src/neurotechdevkit/scenarios/materials.py | 1 + src/neurotechdevkit/sources.py | 32 +++++++++----------- 16 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index 34f32129..ae9e5103 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -18,7 +18,7 @@ class ScenarioNotFoundError(Exception): pass -def make(scenario_id: str, complexity="fast"): +def make(scenario_id, complexity="fast"): """ Initialize a scenario and return an object which represents the simulation. diff --git a/src/neurotechdevkit/rendering/layers.py b/src/neurotechdevkit/rendering/layers.py index e47b75e6..a88dfa5d 100644 --- a/src/neurotechdevkit/rendering/layers.py +++ b/src/neurotechdevkit/rendering/layers.py @@ -23,8 +23,7 @@ def draw_source(ax: plt.Axes, source: SourceDrawingParams) -> None: def draw_target( ax: plt.Axes, target_loc: npt.NDArray[np.float_], target_radius: float ) -> None: - """ - Draw a layer showing the scenario target on top of a figure. + """Draw a layer showing the scenario target on top of a figure. This layer can be added to any scenario figure in 2D. diff --git a/src/neurotechdevkit/rendering/layout.py b/src/neurotechdevkit/rendering/layout.py index 063b98ae..e079f1f3 100644 --- a/src/neurotechdevkit/rendering/layout.py +++ b/src/neurotechdevkit/rendering/layout.py @@ -1,3 +1,4 @@ +"""Functions for rendering a layout plot of a scenario.""" import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np diff --git a/src/neurotechdevkit/rendering/legends.py b/src/neurotechdevkit/rendering/legends.py index eebfa02e..517a38c4 100644 --- a/src/neurotechdevkit/rendering/legends.py +++ b/src/neurotechdevkit/rendering/legends.py @@ -1,3 +1,4 @@ +"""Legends module.""" import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np @@ -10,6 +11,7 @@ class LegendConfig: """A utility class for keeping track of the components needed for a legend.""" def __init__(self): + """Initialize a new LegendConfig.""" self._labels = [] self._handles = [] self._custom_handlers = {} diff --git a/src/neurotechdevkit/rendering/napari.py b/src/neurotechdevkit/rendering/napari.py index ef1e46f8..23ee6f62 100644 --- a/src/neurotechdevkit/rendering/napari.py +++ b/src/neurotechdevkit/rendering/napari.py @@ -1,3 +1,4 @@ +"""Napari module.""" from __future__ import annotations from typing import NamedTuple, Protocol diff --git a/src/neurotechdevkit/rendering/simulations.py b/src/neurotechdevkit/rendering/simulations.py index 86c2dcb1..84dbc45f 100644 --- a/src/neurotechdevkit/rendering/simulations.py +++ b/src/neurotechdevkit/rendering/simulations.py @@ -1,3 +1,4 @@ +"""Simulation rendering functions.""" from __future__ import annotations import matplotlib.pyplot as plt diff --git a/src/neurotechdevkit/scenarios/__init__.py b/src/neurotechdevkit/scenarios/__init__.py index e11cdd99..2da27981 100644 --- a/src/neurotechdevkit/scenarios/__init__.py +++ b/src/neurotechdevkit/scenarios/__init__.py @@ -1,3 +1,4 @@ +"""All scenarios and results.""" from . import materials from ._base import Scenario, Scenario2D, Scenario3D from ._results import ( diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index dcca8943..7b2c122f 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -62,7 +62,7 @@ def __init__( origin: npt.NDArray[np.float_], complexity: str = "fast", ): - """Initialize the scenario""" + """Initialize the scenario.""" self._complexity = complexity if self._complexity != "fast": raise ValueError("the only complexity currently supported is 'fast'") @@ -123,6 +123,7 @@ def dx(self) -> float: @property def ppw(self) -> float: + """The number of points per wavelength.""" # maybe choose lowest speed of sound? raise NotImplementedError() @@ -164,6 +165,7 @@ def dt(self) -> float: @property def ppp(self) -> float: + """The number of points per period.""" raise NotImplementedError() @property @@ -173,6 +175,7 @@ def current_target_id(self) -> str: @current_target_id.setter def current_target_id(self, target_id: str) -> None: + """Set the id of the currently selected target.""" if target_id not in self._TARGET_OPTIONS: raise ValueError( f"{target_id} is not a valid target id." @@ -230,15 +233,10 @@ def ordered_layers(self) -> list[str]: def _material_layers(self) -> list[tuple[str, Struct]]: pass - @property - def material_properties(self): - raise NotImplementedError() - @property @abc.abstractmethod def _material_outline_upsample_factor(self) -> int: - """The value of upsample_factor to use for this scenario when drawing material - outlines. + """Upsample_factor to use for this scenario when drawing material outlines. This parameter is internal to ndk, is not intended to be used directly. """ @@ -865,13 +863,17 @@ def render_material_property( show_sources: bool = True, show_target: bool = True, ) -> None: + """Render a material property for the scenario.""" # speed of sound, density, and absorption # maybe split these out into 3 separate functions? pass class Scenario2D(Scenario): + """A 2D scenario.""" + def get_target_mask(self) -> npt.NDArray[np.bool_]: + """Return the mask for the target region.""" target_mask = create_grid_circular_mask( grid=self.problem.grid, origin=self.origin, @@ -942,6 +944,8 @@ def render_layout( class Scenario3D(Scenario): + """A 3D scenario.""" + @abc.abstractmethod def get_default_slice_axis(self) -> int: """Return the default slice_axis for this scenario. @@ -976,12 +980,14 @@ def viewer_config_3d(self) -> rendering.ViewerConfig3D: pass def get_target_mask(self) -> npt.NDArray[np.bool_]: + """Return the mask for the target region.""" target_mask = create_grid_spherical_mask( grid=self.problem.grid, origin=self.origin, center=self.target_center, radius=self.target_radius, ) + """Return the mask for the target region.""" return target_mask def simulate_pulse( diff --git a/src/neurotechdevkit/scenarios/_metrics.py b/src/neurotechdevkit/scenarios/_metrics.py index 3f743957..55deefaf 100644 --- a/src/neurotechdevkit/scenarios/_metrics.py +++ b/src/neurotechdevkit/scenarios/_metrics.py @@ -218,8 +218,10 @@ def calculate_i_ta_target(result: results.SteadyStateResult) -> float: def calculate_i_ta_off_target(result: results.SteadyStateResult) -> float: - """Calculate the time-averaged intensity within the brain but outside of the target - region. + """Calculate the time-averaged intensity. + + The time-averaged intensity is calculated within the brain but outside of the + target region. The time-averaged intensity is equal to the integral (over time) of the pressure amplitude times 1/(rho*vp). @@ -265,8 +267,10 @@ def calculate_i_pa_target(result: results.SteadyStateResult) -> float: def calculate_i_pa_off_target(result: results.SteadyStateResult) -> float: - """Calculate the pulse-averaged intensity within the brain but outside of the target - region. + """Calculate the pulse-averaged intensity. + + The pulse-averaged intensity is calculated within the brain but outside of the + target region. For steady-state results, the pulse-averaged intensity is equal to the time-average intensity. diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index d9c837fc..e59f4531 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -1052,9 +1052,6 @@ def _build_animation( ) return animation - def render_pulsed_simulation_animation_3d(self): - raise NotImplementedError("Currently not supported.") - def create_steady_state_result( scenario: scenarios.Scenario, diff --git a/src/neurotechdevkit/scenarios/_scenario_0.py b/src/neurotechdevkit/scenarios/_scenario_0.py index 59d7fa9f..5e1d7006 100644 --- a/src/neurotechdevkit/scenarios/_scenario_0.py +++ b/src/neurotechdevkit/scenarios/_scenario_0.py @@ -14,6 +14,8 @@ class Scenario0(Scenario2D): + """Scenario 0.""" + _SCENARIO_ID = "scenario-0-v0" _TARGET_OPTIONS = { "target_1": Target( @@ -25,6 +27,7 @@ class Scenario0(Scenario2D): } def __init__(self, complexity="fast"): + """Create a new instance of scenario 0.""" self._target_id = "target_1" super().__init__( @@ -35,6 +38,7 @@ def __init__(self, complexity="fast"): def render_material_property( self, name, show_orientation=True, show_sources=True, show_target=True ): + """Render the material property of the scenario.""" raise NotImplementedError() @property @@ -84,6 +88,7 @@ def _compile_problem(self) -> stride.Problem: return problem def get_default_source(self): + """Return the default source for the scenario.""" return FocusedSource2D( position=np.array([0.01, 0.0]), direction=np.array([1.0, 0.0]), diff --git a/src/neurotechdevkit/scenarios/_scenario_1.py b/src/neurotechdevkit/scenarios/_scenario_1.py index 0a67d841..9808070c 100644 --- a/src/neurotechdevkit/scenarios/_scenario_1.py +++ b/src/neurotechdevkit/scenarios/_scenario_1.py @@ -116,6 +116,7 @@ class Scenario1_2D(_Scenario1Mixin, Scenario2D): } def __init__(self, complexity="fast"): + """Instantiate a new scenario 1 2D.""" self._target_id = "target_1" super().__init__( @@ -126,6 +127,7 @@ def __init__(self, complexity="fast"): def render_material_property( self, name, show_orientation=True, show_sources=True, show_target=True ): + """Render a material property for the scenario.""" raise NotImplementedError() def _compile_problem(self) -> stride.Problem: @@ -133,6 +135,7 @@ def _compile_problem(self) -> stride.Problem: return self._compile_scenario_1_problem(extent) def get_default_source(self) -> sources.Source: + """Return the default source for the scenario.""" return sources.FocusedSource2D( position=np.array([0.0, 0.0]), direction=np.array([1.0, 0.0]), @@ -173,6 +176,7 @@ class Scenario1_3D(_Scenario1Mixin, Scenario3D): } def __init__(self, complexity="fast"): + """Instantiate a new scenario 1 3D.""" self._target_id = "target_1" super().__init__( @@ -182,6 +186,7 @@ def __init__(self, complexity="fast"): @property def viewer_config_3d(self) -> rendering.ViewerConfig3D: + """Return the default viewer configuration for the scenario.""" return rendering.ViewerConfig3D( init_angles=(-15, 45, 160), init_zoom=3.0, @@ -202,15 +207,18 @@ def viewer_config_3d(self) -> rendering.ViewerConfig3D: ) def get_default_slice_axis(self) -> int: + """Return the default slice axis for the scenario.""" return 1 def get_default_slice_position(self, axis: int) -> float: + """Return the default slice position for the scenario.""" default_positions = np.array([0.064, 0.0, 0.0]) return default_positions[axis] def render_material_property( self, name, show_orientation=True, show_sources=True, show_target=True ): + """Render a material property for the scenario.""" raise NotImplementedError() def _compile_problem(self) -> stride.Problem: @@ -218,6 +226,7 @@ def _compile_problem(self) -> stride.Problem: return self._compile_scenario_1_problem(extent) def get_default_source(self) -> sources.Source: + """Return the default source for the scenario.""" return sources.FocusedSource3D( position=np.array([0.0, 0.0, 0.0]), direction=np.array([1.0, 0.0, 0.0]), diff --git a/src/neurotechdevkit/scenarios/_scenario_2.py b/src/neurotechdevkit/scenarios/_scenario_2.py index 2fb81d33..fc4078d4 100644 --- a/src/neurotechdevkit/scenarios/_scenario_2.py +++ b/src/neurotechdevkit/scenarios/_scenario_2.py @@ -16,7 +16,7 @@ class _Scenario2MixinProtocol(Protocol): - """Provide type-hinting for Scenario 2 members used by mixins""" + """Provide type-hinting for Scenario 2 members used by mixins.""" @property def scenario_id(self) -> str: @@ -138,6 +138,7 @@ class Scenario2_2D(_Scenario2Mixin, Scenario2D): } def __init__(self, complexity="fast"): + """Create a new instance of scenario 2.""" self._target_id = "primary-visual-cortex" super().__init__( @@ -148,6 +149,7 @@ def __init__(self, complexity="fast"): def render_material_property( self, name, show_orientation=True, show_sources=True, show_target=True ): + """Render the material property of the scenario.""" raise NotImplementedError() def _compile_problem(self) -> stride.Problem: @@ -161,6 +163,7 @@ def _get_material_masks(self) -> Mapping[str, npt.NDArray[np.bool_]]: } def get_default_source(self) -> sources.Source: + """Get the default source for the scenario.""" return sources.FocusedSource2D( position=np.array([0.0, 0.0]), direction=np.array([1.0, 0.0]), @@ -244,6 +247,7 @@ class Scenario2_3D(_Scenario2Mixin, Scenario3D): } def __init__(self, complexity="fast"): + """Create a new instance of scenario 2 3D.""" self._target_id = "primary-visual-cortex" super().__init__( @@ -253,6 +257,7 @@ def __init__(self, complexity="fast"): @property def viewer_config_3d(self) -> rendering.ViewerConfig3D: + """Get the default viewer configuration for the scenario.""" return rendering.ViewerConfig3D( init_angles=(90, 10, -60), init_zoom=2.0, @@ -269,15 +274,18 @@ def viewer_config_3d(self) -> rendering.ViewerConfig3D: ) def get_default_slice_axis(self) -> int: + """Get the default slice axis for the scenario.""" return 2 def get_default_slice_position(self, axis: int) -> float: + """Get the default slice position for the scenario.""" default_positions = np.array([0.1, 0.0, 0.0]) return default_positions[axis] def render_material_property( self, name, show_orientation=True, show_sources=True, show_target=True ): + """Render the material property of the scenario.""" raise NotImplementedError() def _compile_problem(self) -> stride.Problem: @@ -291,6 +299,7 @@ def _get_material_masks(self): } def get_default_source(self): + """Get the default source for the scenario.""" return sources.FocusedSource3D( position=np.array([0.0, 0.0, 0.0]), direction=np.array([1.0, 0.0, 0.0]), diff --git a/src/neurotechdevkit/scenarios/_utils.py b/src/neurotechdevkit/scenarios/_utils.py index 7394fcf6..22251932 100644 --- a/src/neurotechdevkit/scenarios/_utils.py +++ b/src/neurotechdevkit/scenarios/_utils.py @@ -50,6 +50,7 @@ def make_grid( def compute_shape(extent: npt.NDArray[np.float_], dx: float) -> tuple[int, ...]: + """Compute the shape of the grid for a given extent and dx.""" # TODO: verify that extent is a multiple of dx # but using modulus doesn't work due to floating point # numerical error diff --git a/src/neurotechdevkit/scenarios/materials.py b/src/neurotechdevkit/scenarios/materials.py index 05d0c61c..78222f25 100644 --- a/src/neurotechdevkit/scenarios/materials.py +++ b/src/neurotechdevkit/scenarios/materials.py @@ -1,3 +1,4 @@ +"""Materials for the neurotechdevkit scenarios.""" from mosaic.types import Struct # TODO: encapsulate mosaic struct behind an NDK materials type diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index d67c8a12..0f5ee5e9 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -1,3 +1,4 @@ +"""Sources module.""" from __future__ import annotations import abc @@ -43,6 +44,7 @@ def __init__( num_points: int, delay: float = 0.0, ) -> None: + """Initialize a new Source object.""" self._validate_delay(delay) self._position = position @@ -55,7 +57,7 @@ def __init__( @property def coordinates(self) -> npt.NDArray[np.float_]: - """A 2D array containing the coordinates (in meters) of the source points. + """A 2D array containing the `coordinates` (in meters) of the source points. The length of this array along the first dimension is equal to `num_points`. """ @@ -63,7 +65,7 @@ def coordinates(self) -> npt.NDArray[np.float_]: @property def position(self) -> npt.NDArray[np.float_]: - """A numpy float array indicating the position (in meters) of the source. + """A numpy float array indicating the `position` (in meters) of the source. The position of the source is defined as the coordinates of the point at the center of symmetry of the source. @@ -97,7 +99,7 @@ def num_points(self) -> int: @property def delay(self) -> float: - """The delay (in seconds) for the source as a whole. + """The `delay` (in seconds) for the source as a whole. `delay` should be non-negative. """ @@ -150,11 +152,6 @@ def calculate_waveform_scale(self, dx: float) -> float: """ pass - def waveform(self, foo, bar, baz): - # TODO: make it the responsibility of the source - # to calculate its own waveform - pass - class FocusedSource2D(Source): """A focused source in 2D. @@ -327,8 +324,7 @@ def _calculate_source_density( @staticmethod def _calculate_threshold(aperture: float, radius: float) -> float: - """Calculate the threshold value to pass to Stride's - `geometries.ellipsoidal` utility function. + """Calculate the threshold value to pass to Stride's. The `threshold` is used by Stride function `geometries.ellipsoidal` and is a number in the range [0.0, 1.0] (inclusive) which corresponds to the percent of @@ -354,8 +350,7 @@ def _calculate_threshold(aperture: float, radius: float) -> float: def _calculate_rotation_parameters( unit_direction: npt.NDArray[np.float_], ) -> tuple[npt.NDArray[np.float_], float]: - """Calculate the rotational parameters to pass to Stride's - `geometries.ellipsoidal` utility function. + """Calculate the rotational parameters for Stride `geometries.ellipsoidal` func. The bowl is originally created with the axis of symmetry along the z-axis, and then it is rotated by `theta` radians around `axis` to align the source along @@ -413,6 +408,7 @@ def __init__( num_points: int, delay: float = 0.0, ) -> None: + """Initialize a new unfocused source.""" super().__init__( position=position, direction=direction, @@ -591,6 +587,7 @@ def _calculate_focus_tilt_element_delays( class PhasedArrayMixin: """A mixin class for phased array sources. + Args: position (npt.NDArray[np.float_]): a numpy float array indicating the coordinates (in meters) of the point at the center of the @@ -635,7 +632,7 @@ def __init__( delay: float = 0.0, element_delays: npt.NDArray[np.float_] | None = None, ) -> None: - + """Initialize a new phased array source.""" self._validate_input_configuration( tilt_angle=tilt_angle, focal_length=focal_length, @@ -672,7 +669,7 @@ def num_elements(self) -> int: @property def pitch(self) -> float: - """The pitch (in meters) of the source.""" + """The `pitch` (in meters) of the source.""" return self._pitch @property @@ -716,7 +713,7 @@ def point_source_delays(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float @property def element_positions(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float_]: - """An array with the position of the center of each element of the array""" + """An array with the position of the center of each element of the array.""" positions = np.zeros(shape=(self.num_elements, len(self.position))) point_mapping = self.point_mapping coords = self.coordinates @@ -1182,7 +1179,7 @@ def __init__( delay: float = 0.0, element_delays: npt.NDArray[np.float_] | None = None, ) -> None: - + """Initialize a new phased array source.""" self._height = height self._unit_center_line = self._validate_center_line(center_line, direction) @@ -1201,7 +1198,7 @@ def __init__( @property def height(self) -> float: - """The height (in meters) of the elements of the source.""" + """The `height` (in meters) of the elements of the source.""" return self._height @property @@ -1477,6 +1474,7 @@ def calculate_waveform_scale(self, dx: float) -> float: def _rotate_2d(coords: npt.NDArray[np.float_], theta: float) -> npt.NDArray[np.float_]: """Rotates `coords` around the origin an angle `theta` around the origin (0, 0). + Rotation is in 2D. Args: From d8265fd1d6a1d9f22395a8b5b12da4ebcbdea629 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Mon, 8 May 2023 16:56:37 +0000 Subject: [PATCH 034/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8567ffa5..6e332161 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.20" +version = "v0.0.21" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 1d301be9751073ec7e8989e8a8f200846403d62a Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Mon, 8 May 2023 15:58:57 -0300 Subject: [PATCH 035/198] Adding label enforcer to CI --- .github/workflows/check_labels.yml | 28 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check_labels.yml diff --git a/.github/workflows/check_labels.yml b/.github/workflows/check_labels.yml new file mode 100644 index 00000000..d30c0eb0 --- /dev/null +++ b/.github/workflows/check_labels.yml @@ -0,0 +1,28 @@ +name: Pull Request Labels +on: + pull_request: + types: [opened, labeled, unlabeled, synchronize] +jobs: + label: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + outputs: + status: ${{ steps.check-labels.outputs.status }} + steps: + - id: check-labels + uses: mheap/github-action-required-labels@v4 + with: + mode: exactly + count: 1 + labels: "release:patch, release:minor, release:major, norelease" + exit_type: success + do-other: + runs-on: ubuntu-latest + needs: label + steps: + - run: echo SUCCESS + if: needs.label.outputs.status == 'success' + - run: echo FAILURE && exit 1 + if: needs.label.outputs.status == 'failure' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6e0812b..f9fa62cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - id: release uses: rymndhng/release-on-push-action@master with: - bump_version_scheme: patch + bump_version_scheme: norelease - name: Checkout repo uses: actions/checkout@v3 From dc38820d8ab0025e26231642d2a935c2f86df813 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Mon, 8 May 2023 17:37:36 -0300 Subject: [PATCH 036/198] Adding troubleshooting section --- docs/index.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/index.md b/docs/index.md index 777fcf92..30ce02cb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -72,6 +72,7 @@ You can run `NDK` inside a docker container with a couple of steps: The output of the command above contains the URL of a jupyter notebook server, you can open the URL in your browser or connect to it using your IDE. + ## Usage ```python @@ -84,6 +85,30 @@ result.render_steady_state_amplitudes(show_material_outlines=False) ![Simulation](https://raw.githubusercontent.com/agencyenterprise/neurotechdevkit/main/docs/images/simulation_steady_state.png) +### Troubleshooting + +#### `Error: Process completed with exit code 1.` when installing Stride on Windows + + Unfortunately Stride can't be installed on a Windows platform, therefore NDK is also unsupported. + +#### Getting error `codepy.CompileError: module compilation failed` + + This error occurs when the compiler wasn't able to perform the compilation, it can be caused by a environment configuration problem. Check the `DEVITO_ARCH` environment variable, it should be set with the compiler devito will use to compile the code. + + You can find further information in the [Devito](https://github.com/devitocodes/devito/wiki/) documentation. + +#### Getting error `ModuleNotFoundError: No module named 'neurotechdevkit'` + + This error is shown when `neurotechdevkit` is not installed, if you installed it using a virtual environment like poetry you must run the script with `poetry run` or activate the environment. + +#### Getting error `AttributeError: module 'napari' has no attribute 'Viewer'` when calling `render_layout_3d` + + This error is shown when napari is not installed, make sure to run + + `poetry run pip install "napari[all]"` + + and try again. + ### Acknowledgements Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. From 549a6ed4e1910d919a7f9e40de2c9c4a9c75b323 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 08:27:14 -0300 Subject: [PATCH 037/198] Testing norelease --- .github/workflows/test_no_release.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/test_no_release.yml diff --git a/.github/workflows/test_no_release.yml b/.github/workflows/test_no_release.yml new file mode 100644 index 00000000..21482886 --- /dev/null +++ b/.github/workflows/test_no_release.yml @@ -0,0 +1,25 @@ +name: Test norelease label +on: + pull_request: + types: [opened, labeled, unlabeled, synchronize] + +jobs: + test-norelease: + runs-on: ubuntu-latest + + outputs: + version: ${{ steps.release.outputs.version }} + steps: + - id: release + uses: rymndhng/release-on-push-action@master + with: + bump_version_scheme: norelease + + check-release-data: + runs-on: ubuntu-latest + needs: test-norelease + steps: + - run: echo EMPTY VERSION && exit 1 + if: needs.test-norelease.outputs.version == '' + - run: echo NOT EMPTY VERSION + if: needs.test-norelease.outputs.version != '' From 20b102729d7fa1e33a42a36c6e3717801a5e8481 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 08:47:20 -0300 Subject: [PATCH 038/198] bump --- .github/workflows/test_no_release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_no_release.yml b/.github/workflows/test_no_release.yml index 21482886..68d06acf 100644 --- a/.github/workflows/test_no_release.yml +++ b/.github/workflows/test_no_release.yml @@ -21,5 +21,6 @@ jobs: steps: - run: echo EMPTY VERSION && exit 1 if: needs.test-norelease.outputs.version == '' + - run: echo NOT EMPTY VERSION if: needs.test-norelease.outputs.version != '' From 722a1cb0836f74636962cac7623a22886bc371e0 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 09:42:39 -0300 Subject: [PATCH 039/198] Adding required env variable --- .github/workflows/test_no_release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test_no_release.yml b/.github/workflows/test_no_release.yml index 68d06acf..1faf4aeb 100644 --- a/.github/workflows/test_no_release.yml +++ b/.github/workflows/test_no_release.yml @@ -7,6 +7,9 @@ jobs: test-norelease: runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: version: ${{ steps.release.outputs.version }} steps: From 6aaed42368e32d80523e9ea2e7f01508de70d540 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 09:47:42 -0300 Subject: [PATCH 040/198] Testing dry run --- .github/workflows/test_no_release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_no_release.yml b/.github/workflows/test_no_release.yml index 1faf4aeb..d5487934 100644 --- a/.github/workflows/test_no_release.yml +++ b/.github/workflows/test_no_release.yml @@ -16,7 +16,8 @@ jobs: - id: release uses: rymndhng/release-on-push-action@master with: - bump_version_scheme: norelease + bump_version_scheme: patch + dry_run: True check-release-data: runs-on: ubuntu-latest From 7484c88f4f800b51b87067f31979b9c5a10435fe Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 09:53:53 -0300 Subject: [PATCH 041/198] Not generating a release when release-on-push-action doesn't generate a new tag --- .github/workflows/release.yml | 13 +++++++++--- .github/workflows/test_no_release.yml | 30 --------------------------- 2 files changed, 10 insertions(+), 33 deletions(-) delete mode 100644 .github/workflows/test_no_release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9fa62cf..e94a8a4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,18 +5,25 @@ on: - main jobs: - release-on-push: + generate-release: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + version: ${{ steps.release.outputs.version }} + steps: - id: release uses: rymndhng/release-on-push-action@master with: bump_version_scheme: norelease + push-release: + runs-on: ubuntu-latest + needs: generate-release + steps: - name: Checkout repo uses: actions/checkout@v3 with: @@ -32,7 +39,7 @@ jobs: - name: Update poetry version run: | - poetry version ${{ steps.release.outputs.version }} + poetry version ${{ needs.generate-release.outputs.version }} - name: Commit poetry version uses: stefanzweifel/git-auto-commit-action@v4 @@ -58,5 +65,5 @@ jobs: - name: Build the neurotechdevkit docker image run: | - docker build . --tag ghcr.io/agencyenterprise/neurotechdevkit:latest --tag ghcr.io/agencyenterprise/neurotechdevkit:${{ steps.release.outputs.version }} + docker build . --tag ghcr.io/agencyenterprise/neurotechdevkit:latest --tag ghcr.io/agencyenterprise/neurotechdevkit:${{ needs.generate-release.outputs.version }} docker push --all-tags ghcr.io/agencyenterprise/neurotechdevkit diff --git a/.github/workflows/test_no_release.yml b/.github/workflows/test_no_release.yml deleted file mode 100644 index d5487934..00000000 --- a/.github/workflows/test_no_release.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Test norelease label -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - -jobs: - test-norelease: - runs-on: ubuntu-latest - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - outputs: - version: ${{ steps.release.outputs.version }} - steps: - - id: release - uses: rymndhng/release-on-push-action@master - with: - bump_version_scheme: patch - dry_run: True - - check-release-data: - runs-on: ubuntu-latest - needs: test-norelease - steps: - - run: echo EMPTY VERSION && exit 1 - if: needs.test-norelease.outputs.version == '' - - - run: echo NOT EMPTY VERSION - if: needs.test-norelease.outputs.version != '' From b309e56985036dc7ab61896b6e7551c415eb9b65 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 11:19:17 -0300 Subject: [PATCH 042/198] Adding --cov to pytest --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index edb06ab1..bf98a552 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ lint-check: poetry run mypy src test: - poetry run pytest tests + poetry run pytest --cov tests test-unit: poetry run pytest tests -m "not integration" From d2cd915d3cbf388885fffe4049ed1a00308a75df Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 11:30:03 -0300 Subject: [PATCH 043/198] Configuring pytest coverage output --- .github/workflows/test.yml | 2 +- Makefile | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9209a39f..142777ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: - name: Run tests run: | - make test + make test-coverage - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/Makefile b/Makefile index bf98a552..60ad3ab1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY:help lint lint-check test test-unit test-integration docs +.PHONY:help lint lint-check test test-coverage test-unit test-integration docs help: @echo "Available commands are: \n*lint, lint-check, test, test-unit, test-integration docs" @@ -16,7 +16,10 @@ lint-check: poetry run mypy src test: - poetry run pytest --cov tests + poetry run pytest tests + +test-coverage: + poetry run pytest . --color=yes --ignore=experiments --ignore=BKP -m "not jitter" --cov=src/neural_data_simulator --cov-report=term-missing:skip-covered --junitxml=pytest.xml --cov-report=xml 2>&1 | tee pytest-coverage.txt test-unit: poetry run pytest tests -m "not integration" From cc2934b60a00fc3d5b98d8847ee6c00802c65e53 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 12:47:42 -0300 Subject: [PATCH 044/198] Replacing auto-generate GITHUB_TOKEN with Personal Access Token, since the main branch is now protected --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6e0812b..2e752307 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TAG_CREATOR_API_TOKEN }} steps: - id: release From b9971fd42f97f9b26922d3bcfe6e95fa5ea1c6d8 Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Tue, 9 May 2023 16:00:15 +0000 Subject: [PATCH 045/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6e332161..0b12727a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.21" +version = "v0.0.26" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 5570bb1fe7dd4c3495d64d2677dce8132911a79b Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 13:28:12 -0300 Subject: [PATCH 046/198] Adding libomp error to troubleshooting --- docs/index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/index.md b/docs/index.md index 30ce02cb..d1caef85 100644 --- a/docs/index.md +++ b/docs/index.md @@ -97,6 +97,13 @@ result.render_steady_state_amplitudes(show_material_outlines=False) You can find further information in the [Devito](https://github.com/devitocodes/devito/wiki/) documentation. +#### Getting error `codepy.CompileError: module compilation failed` with `fatal error: 'omp.h' file not found` + + This error occurs when the `libomp` is not installed or can not be found by the compiler. + + Make sure to install it and export the environment variable `CPATH` with the path to the folder containing libomp headers. + + #### Getting error `ModuleNotFoundError: No module named 'neurotechdevkit'` This error is shown when `neurotechdevkit` is not installed, if you installed it using a virtual environment like poetry you must run the script with `poetry run` or activate the environment. From 71cad18ec3b28fe5634acef4a4a6b606777ee2f7 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 20:25:56 -0300 Subject: [PATCH 047/198] Only running publish release if a new version was generated --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25025c13..dc83d25c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,7 @@ jobs: bump_version_scheme: norelease push-release: + if: needs.generate-release.outputs.version != '' runs-on: ubuntu-latest needs: generate-release steps: From 4fe583dc68206d3263c8f9a9bc138c7403e604f9 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 20:31:41 -0300 Subject: [PATCH 048/198] Removing empty lines --- .github/workflows/release.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc83d25c..4e3a2dda 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,13 +7,10 @@ on: jobs: generate-release: runs-on: ubuntu-latest - env: GITHUB_TOKEN: ${{ secrets.TAG_CREATOR_API_TOKEN }} - outputs: version: ${{ steps.release.outputs.version }} - steps: - id: release uses: rymndhng/release-on-push-action@master From 3e166006d850b2eaf9394e7efdd9bb90e9b53f25 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 20:41:15 -0300 Subject: [PATCH 049/198] Adding Personal Access Token to push release --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e3a2dda..9bbeb879 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,7 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ github.ref }} + token: ${{ secrets.TAG_CREATOR_API_TOKEN }} - name: Setup python uses: actions/setup-python@v4 From 42d2a1d5f8ff3daa544fceb71e3fcf4024a5d68a Mon Sep 17 00:00:00 2001 From: NewtonSander Date: Tue, 9 May 2023 23:44:45 +0000 Subject: [PATCH 050/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 216a15ae..5a479de8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.26" +version = "v0.0.29" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 27fada69a4b54ec62b0c668ffbcefd813f374510 Mon Sep 17 00:00:00 2001 From: Diogo de Lucena <90583560+d-lucena@users.noreply.github.com> Date: Tue, 9 May 2023 21:16:34 -0300 Subject: [PATCH 051/198] Update acknowledgements in index.md (#8) --- docs/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 777fcf92..3f2f6204 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # Welcome to Neurotech Development Kit -The _Neurotech Development Kit_ (NDK) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. +The [_Neurotech Development Kit_ (NDK)](https://agencyenterprise.github.io/neurotechdevkit/) is an open-source software library designed to enhance accessibility to cutting-edge neurotechnology. Featuring an easy-to-use API and pre-built examples, the NDK provides a seamless starting point for users. Moreover, the NDK offers educational resources, such as interactive simulations and notebook-based tutorials, catering to a diverse audience including researchers, educators, engineers, and trainees. By lowering the barrier of entry for newcomers and accelerating the progress of researchers, the NDK aims to be a versatile and invaluable tool for the neurotech community. @@ -14,6 +14,8 @@ The Neurotech Development Kit is actively developed and we welcome feedback and ![Simulation](https://raw.githubusercontent.com/agencyenterprise/neurotechdevkit/main/docs/images/ndk_example.gif) +Check out the [NDK documentation page](https://agencyenterprise.github.io/neurotechdevkit/). + ## Running ### Local installation @@ -86,4 +88,4 @@ result.render_steady_state_amplitudes(show_material_outlines=False) ### Acknowledgements -Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. +Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations and providing an MIT license for usage within NDK, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. From 59be5b87f2e66650afbbc4ec463f537f00cd9d62 Mon Sep 17 00:00:00 2001 From: d-lucena Date: Wed, 10 May 2023 00:17:40 +0000 Subject: [PATCH 052/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5a479de8..7328603c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.29" +version = "v0.0.30" description = "A research repo for the NDK (Neurotech Development Kit) project" authors = ["AE Studio "] maintainers = ["AE Studio "] From 3fa7fc090821c74b6252d578c834f9fdb5af1bd3 Mon Sep 17 00:00:00 2001 From: Diogo de Lucena <90583560+d-lucena@users.noreply.github.com> Date: Wed, 10 May 2023 11:58:13 -0300 Subject: [PATCH 053/198] update project description (#20) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7328603c..054bf61e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "neurotechdevkit" version = "v0.0.30" -description = "A research repo for the NDK (Neurotech Development Kit) project" +description = "Neurotech Development Kit: an open-source software library designed to enhance accessibility to cutting-edge neurotechnology" authors = ["AE Studio "] maintainers = ["AE Studio "] packages = [{include = "neurotechdevkit", from = "src" }] From 5edcdb02ab2aec52d443bf9aa761df60ac6811a7 Mon Sep 17 00:00:00 2001 From: d-lucena Date: Wed, 10 May 2023 15:00:02 +0000 Subject: [PATCH 054/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 054bf61e..053ed25b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.30" +version = "v0.0.31" description = "Neurotech Development Kit: an open-source software library designed to enhance accessibility to cutting-edge neurotechnology" authors = ["AE Studio "] maintainers = ["AE Studio "] From 26d335ab8fb4a49cde2abdbb82b13d4771d3d5e8 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 12:50:36 -0300 Subject: [PATCH 055/198] Moving troubleshooting section to its own page inside /Usage --- docs/index.md | 31 ------------------------------- docs/usage/troubleshooting.md | 30 ++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 docs/usage/troubleshooting.md diff --git a/docs/index.md b/docs/index.md index d1caef85..2ebd1b91 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,37 +85,6 @@ result.render_steady_state_amplitudes(show_material_outlines=False) ![Simulation](https://raw.githubusercontent.com/agencyenterprise/neurotechdevkit/main/docs/images/simulation_steady_state.png) -### Troubleshooting - -#### `Error: Process completed with exit code 1.` when installing Stride on Windows - - Unfortunately Stride can't be installed on a Windows platform, therefore NDK is also unsupported. - -#### Getting error `codepy.CompileError: module compilation failed` - - This error occurs when the compiler wasn't able to perform the compilation, it can be caused by a environment configuration problem. Check the `DEVITO_ARCH` environment variable, it should be set with the compiler devito will use to compile the code. - - You can find further information in the [Devito](https://github.com/devitocodes/devito/wiki/) documentation. - -#### Getting error `codepy.CompileError: module compilation failed` with `fatal error: 'omp.h' file not found` - - This error occurs when the `libomp` is not installed or can not be found by the compiler. - - Make sure to install it and export the environment variable `CPATH` with the path to the folder containing libomp headers. - - -#### Getting error `ModuleNotFoundError: No module named 'neurotechdevkit'` - - This error is shown when `neurotechdevkit` is not installed, if you installed it using a virtual environment like poetry you must run the script with `poetry run` or activate the environment. - -#### Getting error `AttributeError: module 'napari' has no attribute 'Viewer'` when calling `render_layout_3d` - - This error is shown when napari is not installed, make sure to run - - `poetry run pip install "napari[all]"` - - and try again. - ### Acknowledgements Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. diff --git a/docs/usage/troubleshooting.md b/docs/usage/troubleshooting.md new file mode 100644 index 00000000..b57546d6 --- /dev/null +++ b/docs/usage/troubleshooting.md @@ -0,0 +1,30 @@ +This page contains a list of known problems you might face when installing and running NDK and the actions to solve them. + + +### `Error: Process completed with exit code 1.` when installing Stride on Windows + +Unfortunately Stride can't be installed on a Windows platform, therefore NDK is also unsupported. + +### Getting error `codepy.CompileError: module compilation failed` + +This error occurs when the compiler wasn't able to perform the compilation, it can be caused by a environment configuration problem. Check the `DEVITO_ARCH` environment variable, it should be set with the compiler devito will use to compile the code. + +You can find further information in the [Devito](https://github.com/devitocodes/devito/wiki/) documentation. + +### Getting error `codepy.CompileError: module compilation failed` with `fatal error: 'omp.h' file not found` + +This error occurs when the `libomp` is not installed or can not be found by the compiler. + +Make sure to install it and export the environment variable `CPATH` with the path to the folder containing libomp headers. + +### Getting error `ModuleNotFoundError: No module named 'neurotechdevkit'` + +This error is shown when `neurotechdevkit` is not installed, if you installed it using a virtual environment like poetry you must run the script with `poetry run` or activate the environment. + +### Getting error `AttributeError: module 'napari' has no attribute 'Viewer'` when calling `render_layout_3d` + +This error is shown when napari is not installed, make sure to run + + `poetry run pip install "napari[all]"` + +and try again. diff --git a/mkdocs.yml b/mkdocs.yml index 0bbb314f..6e42fb9d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,6 +32,7 @@ nav: - Sources: usage/defining_sources.md - Simulation: usage/running_simulation.md - 3D Visualization: usage/3d.md + - Troubleshooting: usage/troubleshooting.md - API: - Make: api/make.md - Scenarios: api/scenarios.md From 1b36a23ece3528edf802b4785c0e136c4acdee5c Mon Sep 17 00:00:00 2001 From: Diogo de Lucena <90583560+d-lucena@users.noreply.github.com> Date: Wed, 10 May 2023 13:58:54 -0300 Subject: [PATCH 056/198] fix Acknowledgements header level --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 2ebd1b91..639ffc4e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,6 +85,6 @@ result.render_steady_state_amplitudes(show_material_outlines=False) ![Simulation](https://raw.githubusercontent.com/agencyenterprise/neurotechdevkit/main/docs/images/simulation_steady_state.png) -### Acknowledgements +## Acknowledgements Thanks to Fred Ehrsam for supporting this project, Quintin Frerichs and Milan Cvitkovic for providing direction, and to Sumner Norman for his ultrasound and neuroscience expertise. Thanks to [Stride](https://www.stride.codes/) for facilitating ultrasound simulations, [Devito](https://www.devitoproject.org/) for providing the backend solver, [Napari](https://napari.org/stable/) for great 3D visualization, and to [Jean-Francois Aubry, et al.](https://doi.org/10.1121/10.0013426) for the basis of the simulation scenarios. From e928ad8afd1a4feba98b2e5376fc81c0ab5ae28e Mon Sep 17 00:00:00 2001 From: Newton Alex Sander Date: Wed, 17 May 2023 10:36:22 -0300 Subject: [PATCH 057/198] Pyright integration (#15) * Add pyright integration * Fixing warnings discovered by pyright * Linting * Fixing pyright warnings * Fixing pyright catches * Fix linting issues * Linting, fixing pyright issues * Installing napari on lint job * Replacing 'if' with assertion * Fixing docstrings * Removing all usages of typing.cast * Disabling pyright reportGeneralTypeIssues for the whole project --------- Co-authored-by: Florin Pop --- .github/workflows/lint.yml | 3 + Makefile | 2 + poetry.lock | 53 +++++++- pyproject.toml | 4 + src/neurotechdevkit/rendering/_animations.py | 12 +- src/neurotechdevkit/rendering/_formatting.py | 13 +- src/neurotechdevkit/rendering/_source.py | 9 +- src/neurotechdevkit/rendering/_target.py | 7 +- src/neurotechdevkit/rendering/layers.py | 8 +- src/neurotechdevkit/rendering/layout.py | 22 ++-- src/neurotechdevkit/rendering/legends.py | 29 +++-- src/neurotechdevkit/rendering/napari.py | 8 +- src/neurotechdevkit/rendering/simulations.py | 25 ++-- src/neurotechdevkit/scenarios/_base.py | 18 ++- src/neurotechdevkit/scenarios/_scenario_0.py | 10 +- src/neurotechdevkit/scenarios/_shots.py | 3 +- src/neurotechdevkit/scenarios/_time.py | 9 +- src/neurotechdevkit/scenarios/_utils.py | 1 + src/neurotechdevkit/sources.py | 123 ++++--------------- tests/neurotechdevkit/test_sources.py | 98 +++++++++------ 20 files changed, 248 insertions(+), 209 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1689bc4c..c5d8fb9f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,6 +41,9 @@ jobs: - name: Install stride run: poetry run pip install git+https://github.com/trustimaging/stride + - name: Install napari + run: poetry run pip install napari + - name: Run linting run: | make lint-check diff --git a/Makefile b/Makefile index a7109ce6..c413887b 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ lint: poetry run mypy src poetry run codespell src poetry run pydocstyle src + poetry run pyright lint-check: poetry run isort --check src tests @@ -18,6 +19,7 @@ lint-check: poetry run mypy src poetry run codespell src poetry run pydocstyle src + poetry run pyright --warnings test: poetry run pytest tests diff --git a/poetry.lock b/poetry.lock index b5668c52..57129ba9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2607,6 +2607,21 @@ files = [ {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, ] +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "notebook" version = "6.5.4" @@ -3132,6 +3147,25 @@ contexttimer = "*" [package.extras] compression = ["blosc", "pyzfp"] +[[package]] +name = "pyright" +version = "1.1.307" +description = "Command line wrapper for pyright" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.307-py3-none-any.whl", hash = "sha256:6b360d2e018311bdf8acea73ef1f21bf0b5b502345aa94bc6763cf197b2e75b3"}, + {file = "pyright-1.1.307.tar.gz", hash = "sha256:b7a8734fad4a2438b8bb0dfbe462f529c9d4eb31947bdae85b9b4e7a97ff6a49"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pyrsistent" version = "0.19.3" @@ -3689,6 +3723,23 @@ nativelib = ["pyobjc-framework-Cocoa", "pywin32"] objc = ["pyobjc-framework-Cocoa"] win32 = ["pywin32"] +[[package]] +name = "setuptools" +version = "67.7.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, + {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -4186,4 +4237,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "e92bf8713fcb14b083b12c11f138ae8d95ce925505afe53f027919c49edbb946" +content-hash = "6eab2b13f33428254f8196f3b57e29489bd1210de66e9539d24b089eb4ede0fa" diff --git a/pyproject.toml b/pyproject.toml index 053ed25b..d16f3d3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ mkdocs-gallery = "^0.7.6" mkdocstrings = {extras = ["python"], version = "^0.21.2"} codespell = "^2.2.4" pydocstyle = "^6.3.0" +pyright = "^1.1.306" [build-system] requires = ["poetry-core>=1.0.0"] @@ -81,5 +82,8 @@ markers = [ "integration: marks tests that are integration tests." ] +[tool.pyright] +include = ["src"] +reportGeneralTypeIssues = "none" # [tool.flake8] # flake8 does not support config in pyproject.toml, see .flake8 diff --git a/src/neurotechdevkit/rendering/_animations.py b/src/neurotechdevkit/rendering/_animations.py index ce90cc03..29dbb5e1 100644 --- a/src/neurotechdevkit/rendering/_animations.py +++ b/src/neurotechdevkit/rendering/_animations.py @@ -2,10 +2,12 @@ from typing import Callable import matplotlib +import matplotlib.axes +import matplotlib.figure import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt -from IPython.display import Video +from IPython.core.display import Video from matplotlib.animation import FFMpegWriter, FuncAnimation ARTIST_NAME = "NDK Research" @@ -66,13 +68,13 @@ def configure_matplotlib_for_embedded_animation() -> None: Changing `animation.embed_limit` is required to have larger animations embedded in the notebook without the need of writing to disk. """ - plt.rcParams["animation.embed_limit"] = 2**100 - plt.rcParams["animation.html"] = "jshtml" + matplotlib.rcParams["animation.embed_limit"] = 2**100 + matplotlib.rcParams["animation.html"] = "jshtml" def make_animation( - fig: plt.Figure, - ax: plt.Axes, + fig: matplotlib.figure.Figure, + ax: matplotlib.axes.Axes, wavefield: npt.NDArray[np.float_], n_frames_undersampling: int, ) -> FuncAnimation: diff --git a/src/neurotechdevkit/rendering/_formatting.py b/src/neurotechdevkit/rendering/_formatting.py index 737ea80f..567bd9a9 100644 --- a/src/neurotechdevkit/rendering/_formatting.py +++ b/src/neurotechdevkit/rendering/_formatting.py @@ -1,11 +1,14 @@ from itertools import chain -import matplotlib.pyplot as plt +import matplotlib.axes +import matplotlib.figure from .font import AXIS_LABEL_FONT_PROPS, AXIS_TICK_FONT_PROPS, TITLE_FONT_PROPS -def configure_title(fig: plt.Figure, title: str, x_pos: float = 0.5) -> None: +def configure_title( + fig: matplotlib.figure.Figure, title: str, x_pos: float = 0.5 +) -> None: """Configure the title of the plot. Note: we might expect that 0.5 in figure coordinates would place the text centered @@ -28,7 +31,7 @@ def configure_title(fig: plt.Figure, title: str, x_pos: float = 0.5) -> None: def configure_axis_labels( - ax: plt.Axes, horizontal_label: str, vertical_label: str + ax: matplotlib.axes.Axes, horizontal_label: str, vertical_label: str ) -> None: """Configure the labels for the X and Y axes. @@ -49,7 +52,7 @@ def configure_axis_labels( ) -def configure_axis_ticks(ax: plt.Axes) -> None: +def configure_axis_ticks(ax: matplotlib.axes.Axes) -> None: """Configure the ticks and tick labels for the X and Y axes. Args: @@ -60,7 +63,7 @@ def configure_axis_ticks(ax: plt.Axes) -> None: label.set_font_properties(AXIS_TICK_FONT_PROPS) -def configure_grid(ax: plt.Axes) -> None: +def configure_grid(ax: matplotlib.axes.Axes) -> None: """Configure the grid for the plot. Args: diff --git a/src/neurotechdevkit/rendering/_source.py b/src/neurotechdevkit/rendering/_source.py index 52e01ea5..00985812 100644 --- a/src/neurotechdevkit/rendering/_source.py +++ b/src/neurotechdevkit/rendering/_source.py @@ -1,6 +1,7 @@ import pathlib from typing import NamedTuple +import matplotlib.artist import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt @@ -8,7 +9,7 @@ from matplotlib.image import BboxImage from matplotlib.transforms import Bbox, Transform, TransformedBbox -from neurotechdevkit.sources import PhasedArrayMixin, Source +from neurotechdevkit.sources import PhasedArraySource, Source _COMPONENT_DIR = pathlib.Path(__file__).parent / "components" @@ -46,7 +47,7 @@ class SourceDrawingParams(NamedTuple): def create_source_drawing_artist( source_params: SourceDrawingParams, transform: Transform -) -> plt.Artist: +) -> matplotlib.artist.Artist: """Create a matplotlib artist for a source rendered inside a scenario. Note that the source coordinates are in scenario coordinates, and not plot @@ -90,7 +91,7 @@ def create_source_drawing_artist( def create_source_legend_artist( loc: npt.NDArray[np.float_], width: float, transform: Transform -) -> plt.Artist: +) -> matplotlib.artist.Artist: """Create a matplotlib artist for a source icon used in a legend. Note that `loc` and `width` should be in legend canvas coordinates. Ideally, they @@ -173,7 +174,7 @@ def source_should_be_flat(source: Source) -> bool: Returns: True if the source is a flat transducer, and False otherwise. """ - is_phased_array = isinstance(source, PhasedArrayMixin) + is_phased_array = isinstance(source, PhasedArraySource) return is_phased_array or np.isinf(source.focal_length) diff --git a/src/neurotechdevkit/rendering/_target.py b/src/neurotechdevkit/rendering/_target.py index 408bb662..959e5bf8 100644 --- a/src/neurotechdevkit/rendering/_target.py +++ b/src/neurotechdevkit/rendering/_target.py @@ -1,5 +1,6 @@ import pathlib +import matplotlib.artist import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt @@ -13,7 +14,7 @@ def create_target_drawing_artist( target_loc: npt.NDArray[np.float_], target_radius: float, transform: Transform, -) -> plt.Artist: +) -> matplotlib.artist.Artist: """Create the matplotlib artist for the target symbol. The caller is expected to provide the transform that takes the coordinates for the @@ -49,7 +50,7 @@ def create_target_legend_artist( center_loc: npt.NDArray[np.float_], target_radius: float, transform: Transform, -) -> plt.Artist: +) -> matplotlib.artist.Artist: """Create the matplotlib artist for the target symbol. The caller is expected to provide the transform that takes the coordinates for the @@ -80,7 +81,7 @@ def create_target_legend_artist( return img_box -def _load_target(version: str) -> npt.NDArray[np.int_]: +def _load_target(version: str) -> npt.NDArray: """Load a png of the target and returns it as a numpy array. The array is returned in RGBA channel order. diff --git a/src/neurotechdevkit/rendering/layers.py b/src/neurotechdevkit/rendering/layers.py index a88dfa5d..f34b602e 100644 --- a/src/neurotechdevkit/rendering/layers.py +++ b/src/neurotechdevkit/rendering/layers.py @@ -1,5 +1,5 @@ """Layers for rendering scenario figures.""" -import matplotlib.pyplot as plt +import matplotlib.axes import numpy as np import numpy.typing as npt @@ -7,7 +7,7 @@ from ._target import create_target_drawing_artist -def draw_source(ax: plt.Axes, source: SourceDrawingParams) -> None: +def draw_source(ax: matplotlib.axes.Axes, source: SourceDrawingParams) -> None: """Draw a layer showing the scenario sources on top of a figure. This layer can be added to any scenario figure in 2D. @@ -21,7 +21,7 @@ def draw_source(ax: plt.Axes, source: SourceDrawingParams) -> None: def draw_target( - ax: plt.Axes, target_loc: npt.NDArray[np.float_], target_radius: float + ax: matplotlib.axes.Axes, target_loc: npt.NDArray[np.float_], target_radius: float ) -> None: """Draw a layer showing the scenario target on top of a figure. @@ -41,7 +41,7 @@ def draw_target( def draw_material_outlines( - ax: plt.Axes, + ax: matplotlib.axes.Axes, material_field: npt.NDArray[np.int_], dx: float, origin: npt.NDArray[np.float_], diff --git a/src/neurotechdevkit/rendering/layout.py b/src/neurotechdevkit/rendering/layout.py index e079f1f3..154e2622 100644 --- a/src/neurotechdevkit/rendering/layout.py +++ b/src/neurotechdevkit/rendering/layout.py @@ -1,5 +1,8 @@ """Functions for rendering a layout plot of a scenario.""" -import matplotlib as mpl +import matplotlib.axes +import matplotlib.colors +import matplotlib.figure +import matplotlib.lines import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt @@ -25,7 +28,7 @@ def create_layout_fig( origin: npt.NDArray[np.float_], color_sequence: list[str], field: npt.NDArray[np.int_], -) -> tuple[plt.Figure, plt.Axes]: +) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: """Create an unformatted figure showing the layout of a scenario. Unformatted means that the data has been plotted, but things like axes ticks and @@ -57,16 +60,15 @@ def create_layout_fig( [origin[1], origin[1] + extent[1], origin[0] + extent[0], origin[0]] ) - cmap = mpl.colors.ListedColormap(color_sequence) + cmap = matplotlib.colors.ListedColormap(color_sequence) clim = (-0.5, len(color_sequence) - 0.5) ax.imshow(field, cmap=cmap, extent=imshow_extent, clim=clim) - return fig, ax def configure_layout_plot( - fig: plt.Figure, - ax: plt.Axes, + fig: matplotlib.figure.Figure, + ax: matplotlib.axes.Axes, color_sequence: list[str], layer_labels: list[str], show_sources: bool, @@ -109,7 +111,7 @@ def configure_layout_plot( def _configure_legend( - ax: plt.Axes, + ax: matplotlib.axes.Axes, layer_labels: list[str], color_sequence: list[str], show_target: bool, @@ -148,7 +150,7 @@ def _configure_legend( ) -def _get_material_layer_handle(color: str) -> plt.Line2D: +def _get_material_layer_handle(color: str) -> matplotlib.lines.Line2D: """Create a legend handle for a material layer in the layout figure. Args: @@ -157,4 +159,6 @@ def _get_material_layer_handle(color: str) -> plt.Line2D: Returns: A handle for the layer linestyle. """ - return plt.Line2D([], [], color=color, linestyle="", marker="s", markersize=12) + return matplotlib.lines.Line2D( + [], [], color=color, linestyle="", marker="s", markersize=12 + ) diff --git a/src/neurotechdevkit/rendering/legends.py b/src/neurotechdevkit/rendering/legends.py index 517a38c4..bcf5a9a5 100644 --- a/src/neurotechdevkit/rendering/legends.py +++ b/src/neurotechdevkit/rendering/legends.py @@ -1,6 +1,10 @@ """Legends module.""" -import matplotlib as mpl -import matplotlib.pyplot as plt +from typing import Optional + +import matplotlib.artist +import matplotlib.legend +import matplotlib.legend_handler +import matplotlib.offsetbox import numpy as np from ._source import create_source_legend_artist @@ -20,7 +24,7 @@ def append_item( self, label: str, handle: object, - custom_handler: mpl.legend_handler.HandlerBase = None, + custom_handler: Optional[matplotlib.legend_handler.HandlerBase] = None, ) -> None: """Add a new legend item to the config. @@ -58,7 +62,7 @@ def get_handles(self) -> list[object]: """ return self._handles.copy() - def get_custom_handlers(self) -> dict[type, mpl.legend_handler.HandlerBase]: + def get_custom_handlers(self) -> dict[type, matplotlib.legend_handler.HandlerBase]: """Return a map containing custom legend handlers. Returns: @@ -77,7 +81,7 @@ class TargetHandle: pass -class TargetHandler: +class TargetHandler(matplotlib.legend_handler.HandlerBase): """A legend handler to draw the target symbol in a legend. This class implements the required `legend_artist` . See @@ -87,11 +91,11 @@ class TargetHandler: def legend_artist( self, - legend: mpl.legend.Legend, + legend: matplotlib.legend.Legend, orig_handle: TargetHandle, fontsize: float, - handlebox: mpl.offsetbox.OffsetBox, - ) -> plt.Artist: + handlebox: matplotlib.offsetbox.DrawingArea, + ) -> matplotlib.artist.Artist: """Return the artist that draws the target in the legend. Args: @@ -107,7 +111,6 @@ def legend_artist( An artist that draws the target in the legend. """ TARGET_LEGEND_SCALE_FACTOR = 1.5 - center = np.array( # in scenario coordinates [ handlebox.xdescent + handlebox.width / 2, @@ -135,7 +138,7 @@ class SourceHandle: pass -class SourceHandler: +class SourceHandler(matplotlib.legend_handler.HandlerBase): """A legend handler to draw the source symbol in a legend. This class implements the required `legend_artist` . See @@ -145,11 +148,11 @@ class SourceHandler: def legend_artist( self, - legend: mpl.legend.Legend, + legend: matplotlib.legend.Legend, orig_handle: TargetHandle, fontsize: float, - handlebox: mpl.offsetbox.OffsetBox, - ) -> tuple[plt.Artist, ...]: + handlebox: matplotlib.offsetbox.DrawingArea, + ) -> matplotlib.artist.Artist: """Return the artist that draws the source in the legend. Args: diff --git a/src/neurotechdevkit/rendering/napari.py b/src/neurotechdevkit/rendering/napari.py index 23ee6f62..fc21dd0a 100644 --- a/src/neurotechdevkit/rendering/napari.py +++ b/src/neurotechdevkit/rendering/napari.py @@ -28,7 +28,7 @@ def add_image( rendering="mip", iso_threshold=0.5, colormap=None, - opacity=1, + opacity=1.0, ): pass @@ -40,7 +40,7 @@ def add_shapes( shape_type="rectangle", edge_color="#777777", face_color="#white", - edge_width=1, + edge_width=1.0, opacity=0.7, ): pass @@ -51,10 +51,10 @@ def add_points( *, name=None, symbol="o", - size=10, + size=10.0, face_color="white", edge_color="dimgray", - opacity=1, + opacity=1.0, ): pass diff --git a/src/neurotechdevkit/rendering/simulations.py b/src/neurotechdevkit/rendering/simulations.py index 84dbc45f..65defe9f 100644 --- a/src/neurotechdevkit/rendering/simulations.py +++ b/src/neurotechdevkit/rendering/simulations.py @@ -1,10 +1,12 @@ """Simulation rendering functions.""" from __future__ import annotations +import matplotlib as mpl +import matplotlib.axes +import matplotlib.figure import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt -from matplotlib import cm from matplotlib.colors import LinearSegmentedColormap from ._formatting import ( @@ -27,7 +29,7 @@ def create_steady_state_figure( extent: npt.NDArray[np.float_], origin: npt.NDArray[np.float_], amplitudes: npt.NDArray[np.float_], -) -> tuple[plt.Figure, plt.Axes]: +) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: """Create an unformatted figure containing the steady-state pressure amplitude. Unformatted means that the data has been plotted, but things like axes ticks and @@ -65,7 +67,7 @@ def create_pulsed_figure( extent: npt.NDArray[np.float_], wavefield: npt.NDArray[np.float_], norm: str = "linear", -) -> tuple[plt.Figure, plt.Axes]: +) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: """Create a base figure containing the pulsed wavefield pressures. The figure is used as a template to created frames for an animation. The colorbar @@ -109,8 +111,8 @@ def create_pulsed_figure( def configure_result_plot( - fig: plt.Figure, - ax: plt.Axes, + fig: matplotlib.figure.Figure, + ax: matplotlib.axes.Axes, show_sources: bool, show_target: bool, extent: npt.NDArray[np.float_], @@ -157,7 +159,9 @@ def configure_result_plot( def _configure_colorbar( - fig: plt.Figure, ax: plt.Axes, clim: tuple[float, float] | None = None + fig: matplotlib.figure.Figure, + ax: matplotlib.axes.Axes, + clim: tuple[float, float] | None = None, ) -> None: """Configure the colorbar for the steady-state amplitude plot. @@ -176,7 +180,9 @@ def _configure_colorbar( label.set_font_properties(AXIS_TICK_FONT_PROPS) -def _configure_legend(ax: plt.Axes, show_sources: bool, show_target: bool) -> None: +def _configure_legend( + ax: matplotlib.axes.Axes, show_sources: bool, show_target: bool +) -> None: """Configure the legend for the steady-state amplitude plot. Args: @@ -223,8 +229,9 @@ def _create_centered_bidirectional_cmap( """ ratio = np.abs(vmin / vmax) n_points = 128 - colors1 = cm.viridis(np.linspace(0, 1, int(n_points * ratio))) - colors2 = cm.viridis(np.linspace(0.0, 1, n_points)) + cmap = mpl.colormaps["viridis"] + colors1 = cmap(np.linspace(0, 1, int(n_points * ratio))) + colors2 = cmap(np.linspace(0.0, 1, n_points)) def modify_viridis(color, it, delta_color=1 / int(n_points * ratio)): """Gradually modifies the color of the original color map.""" diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index 7b2c122f..b8f01d05 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -3,6 +3,7 @@ import abc import asyncio from dataclasses import dataclass +from types import SimpleNamespace from typing import Mapping import nest_asyncio @@ -11,6 +12,7 @@ import stride from frozenlist import FrozenList from mosaic.types import Struct +from stride.problem import StructuredData from .. import rendering, scenarios from ..sources import Source @@ -101,6 +103,7 @@ def problem(self) -> stride.Problem: @property def extent(self) -> npt.NDArray[np.float_]: """The extent of the spatial grid (in meters).""" + assert self.problem.space is not None return np.array(self.problem.space.size, dtype=float) @property @@ -111,6 +114,7 @@ def origin(self) -> npt.NDArray[np.float_]: @property def shape(self) -> npt.NDArray[np.int_]: """The shape of the spatial grid (in number of grid points).""" + assert self.problem.space is not None return np.array(self.problem.space.shape, dtype=int) @property @@ -119,6 +123,7 @@ def dx(self) -> float: Spacing is the same in each spatial direction. """ + assert self.problem.space is not None return self.problem.space.spacing[0] @property @@ -437,10 +442,11 @@ def simulate_steady_state( wavefield_slice=self._wavefield_slice(), n_jobs=n_jobs, ) + assert isinstance(pde.wavefield, (StructuredData, SimpleNamespace)) + assert sub_problem.shot is not None # put the time axis last and remove the empty last frame wavefield = np.moveaxis(pde.wavefield.data[:-1], 0, -1) - return scenarios.create_steady_state_result( scenario=self, center_frequency=center_frequency, @@ -593,6 +599,8 @@ def _simulate_pulse( wavefield_slice=self._wavefield_slice(slice_axis, slice_position), n_jobs=n_jobs, ) + assert isinstance(pde.wavefield, (StructuredData, SimpleNamespace)) + assert sub_problem.shot is not None # put the time axis last and remove the empty last frame wavefield = np.moveaxis(pde.wavefield.data[:-1], 0, -1) @@ -645,6 +653,7 @@ def _setup_shot( the `Shot` to use for the simulation. """ problem = self.problem + assert problem.grid.time is not None wavelet_name = choose_wavelet_for_mode(simulation_mode) wavelet = wavelet_helper( @@ -652,7 +661,7 @@ def _setup_shot( ) return create_shot(problem, sources, self.origin, wavelet, self.dx) - def _create_pde(self) -> stride.Operator: + def _create_pde(self) -> stride.IsoAcousticDevito: """Instantiate the stride `Operator` representing the PDE for the scenario. All existing scenarios use the `IsoAcousticDevito` operator. @@ -691,7 +700,7 @@ def _wavefield_slice( A tuple of slices defining the region of the grid to record. """ space = self.problem.space - + assert space is not None standard_slice = tuple( [ # save all time points @@ -793,6 +802,7 @@ def _get_steady_state_recording_time_bounds( """ n_frames = ppp * n_cycles time = self.problem.time + assert time is not None return (time.num - n_frames, time.num - 1) def _get_pulsed_recording_time_bounds(self) -> tuple[int, int]: @@ -805,6 +815,7 @@ def _get_pulsed_recording_time_bounds(self) -> tuple[int, int]: be recorded for a pulsed simulation. """ time = self.problem.time + assert time is not None return (0, time.num - 1) def _execute_pde( @@ -837,6 +848,7 @@ def _execute_pde( devito_args = {} if n_jobs is not None: devito_args = dict(nthreads=n_jobs) + assert sub_problem.shot is not None loop = asyncio.get_event_loop() return loop.run_until_complete( pde( diff --git a/src/neurotechdevkit/scenarios/_scenario_0.py b/src/neurotechdevkit/scenarios/_scenario_0.py index 5e1d7006..b60f8df0 100644 --- a/src/neurotechdevkit/scenarios/_scenario_0.py +++ b/src/neurotechdevkit/scenarios/_scenario_0.py @@ -125,8 +125,8 @@ def _create_scenario_0_mask(material, grid, origin): def _create_skull_interface_mask(grid, origin): - skull_outer_radii = (0.01275, 0.01) - skull_center = (0.025, 0.0) + skull_outer_radii = np.array([0.01275, 0.01]) + skull_center = np.array([0.025, 0.0]) skull_a, skull_b = skull_outer_radii outer_skull_mask = create_grid_elliptical_mask( @@ -138,9 +138,9 @@ def _create_skull_interface_mask(grid, origin): def _create_brain_interface_mask(grid, origin): - skull_outer_radii = (0.01275, 0.01) + skull_outer_radii = np.array([0.01275, 0.01]) skull_thickness = 0.001 - skull_center = (0.025, 0.0) + skull_center = np.array([0.025, 0.0]) skull_a, skull_b = skull_outer_radii brain_center = skull_center @@ -156,6 +156,6 @@ def _create_brain_interface_mask(grid, origin): def _create_tumor_mask(grid, origin): tumor_radius = 0.0013 - tumor_center = (0.0285, 0.0025) + tumor_center = np.array([0.0285, 0.0025]) tumor_mask = create_grid_circular_mask(grid, origin, tumor_center, tumor_radius) return tumor_mask diff --git a/src/neurotechdevkit/scenarios/_shots.py b/src/neurotechdevkit/scenarios/_shots.py index f65cfdbd..41575594 100644 --- a/src/neurotechdevkit/scenarios/_shots.py +++ b/src/neurotechdevkit/scenarios/_shots.py @@ -44,7 +44,8 @@ def create_shot( receivers=[], geometry=problem.geometry, ) - + assert shot.wavelets is not None + assert problem.time is not None shot.wavelets.data[:] = _build_shot_wavelets_array( wavelet=wavelet, sources=sources, diff --git a/src/neurotechdevkit/scenarios/_time.py b/src/neurotechdevkit/scenarios/_time.py index f976c7de..cd5cd7d2 100644 --- a/src/neurotechdevkit/scenarios/_time.py +++ b/src/neurotechdevkit/scenarios/_time.py @@ -16,7 +16,7 @@ def select_simulation_time_for_steady_state( time_to_steady_state: float | None, n_cycles_steady_state: int, delay: float, -) -> stride.Time: +) -> float: """Determine how much time (in seconds) to simulate for a steady-state simulation. In order to reach near-steady-state conditions, we need the simulation to run for @@ -44,11 +44,13 @@ def select_simulation_time_for_steady_state( """ if time_to_steady_state is None: min_speed_of_sound = min([m.vp for m in materials.values()]) + assert grid.space is not None diagonal_length = np.linalg.norm(grid.space.size) time_to_steady_state = (2 * diagonal_length) / min_speed_of_sound period = 1.0 / freq_hz - sim_time = delay + time_to_steady_state + n_cycles_steady_state * period + time_before = delay + time_to_steady_state + sim_time = time_before + n_cycles_steady_state * period return sim_time @@ -57,7 +59,7 @@ def select_simulation_time_for_pulsed( grid: stride.Grid, materials: Mapping[str, Struct], delay: float, -): +) -> float: """Determine how much time (in seconds) to simulate for a pulsed simulation. For pulsed simulations, we usually want to simulate enough time for the wave to @@ -76,6 +78,7 @@ def select_simulation_time_for_pulsed( The amount of time (in seconds) to simulate. """ min_speed_of_sound = min([m.vp for m in materials.values()]) + assert grid.space is not None diagonal_length = np.linalg.norm(grid.space.size) return delay + diagonal_length / min_speed_of_sound diff --git a/src/neurotechdevkit/scenarios/_utils.py b/src/neurotechdevkit/scenarios/_utils.py index 22251932..6b357a9a 100644 --- a/src/neurotechdevkit/scenarios/_utils.py +++ b/src/neurotechdevkit/scenarios/_utils.py @@ -318,6 +318,7 @@ def _create_nd_ellipse_mask( Returns: The 2D or 3D boolean mask where gridpoints within the ellipse are True. """ + assert grid.space is not None shape = grid.space.shape spacing = grid.space.spacing diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index 0f5ee5e9..e3212143 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -4,7 +4,6 @@ import abc import math import warnings -from typing import Protocol import numpy as np import numpy.typing as npt @@ -393,8 +392,8 @@ def _calculate_rotation_parameters( return axis, theta -class UnfocusedMixin: - """A mixin class for unfocused sources. +class UnfocusedSource(Source): + """A base class for unfocused sources. Automatically sets `focal_length` to `np.inf` """ @@ -416,10 +415,10 @@ def __init__( focal_length=np.inf, num_points=num_points, delay=delay, - ) # type: ignore[call-arg] + ) -class PlanarSource2D(UnfocusedMixin, Source): +class PlanarSource2D(UnfocusedSource): """A planar source in 2D. This source is shaped like a line segment and has no focus. The source is composed @@ -460,7 +459,7 @@ def calculate_waveform_scale(self, dx: float) -> float: return grid_point_density / source_density -class PlanarSource3D(UnfocusedMixin, Source): +class PlanarSource3D(UnfocusedSource): """A planar source in 3D. This source is shaped like a disk and has no focus. It is created by defining a @@ -504,89 +503,8 @@ def calculate_waveform_scale(self, dx: float) -> float: return grid_point_density / source_density -class _PhasedArrayMixinProtocol(Protocol): - """Provide type-hinting for PhasedMixin.""" - - _element_delays: npt.NDArray[np.float_] - - @property - def num_points(self) -> int: - ... - - @property - def num_elements(self) -> int: - ... - - @property - def pitch(self) -> float: - ... - - @property - def tilt_angle(self) -> float: - ... - - @property - def position(self) -> npt.NDArray[np.float_]: - ... - - @property - def coordinates(self) -> npt.NDArray[np.float_]: - ... - - @property - def point_source_delays(self) -> npt.NDArray[np.float_]: - ... - - @property - def delay(self) -> float: - ... - - @property - def focal_length(self) -> float: - ... - - @property - def focal_point(self) -> npt.NDArray[np.float_]: - ... - - @property - def point_mapping(self) -> tuple[slice, ...]: - ... - - @property - def element_positions(self) -> npt.NDArray[np.float_]: - ... - - @property - def element_delays(self) -> npt.NDArray[np.float_]: - ... - - def _broadcast_delays( - self, delays: npt.NDArray[np.float_] - ) -> npt.NDArray[np.float_]: - ... - - def _set_element_delays( - self, - element_delays: npt.NDArray[np.float_] | None, - ) -> npt.NDArray[np.float_]: - ... - - @staticmethod - def txdelay(tilt_angle: float, pitch: float, speed: float = 1500.0) -> float: - ... - - def _calculate_tilt_element_delays(self) -> npt.NDArray[np.float_]: - ... - - def _calculate_focus_tilt_element_delays( - self, speed: float = 1500.0 - ) -> npt.NDArray[np.float_]: - ... - - -class PhasedArrayMixin: - """A mixin class for phased array sources. +class PhasedArraySource(Source): + """A base class for phased array sources. Args: position (npt.NDArray[np.float_]): a numpy float array indicating @@ -658,9 +576,14 @@ def __init__( focal_length=focal_length, num_points=num_points, delay=delay, - ) # type: ignore[call-arg] + ) - self._element_delays = self._set_element_delays(element_delays) # type: ignore + self._element_delays = self._set_element_delays(element_delays) + + @abc.abstractproperty + def focal_point(self) -> npt.NDArray[np.float_]: + """Get or set the coordinates (in meters) of the focal point of the source.""" + ... @property def num_elements(self) -> int: @@ -703,7 +626,7 @@ def element_delays(self) -> npt.NDArray[np.float_]: return self._element_delays @property - def point_source_delays(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float_]: + def point_source_delays(self) -> npt.NDArray[np.float_]: """The delay before emitting (in seconds) for each point source. The delays are computed at the element level. All source points within an @@ -712,7 +635,7 @@ def point_source_delays(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float return self._broadcast_delays(self.element_delays) @property - def element_positions(self: _PhasedArrayMixinProtocol) -> npt.NDArray[np.float_]: + def element_positions(self) -> npt.NDArray[np.float_]: """An array with the position of the center of each element of the array.""" positions = np.zeros(shape=(self.num_elements, len(self.position))) point_mapping = self.point_mapping @@ -891,7 +814,7 @@ def _translate( return coords def _broadcast_delays( - self: _PhasedArrayMixinProtocol, delays: npt.NDArray[np.float_] + self, delays: npt.NDArray[np.float_] ) -> npt.NDArray[np.float_]: """Translate the delays per element into delays per source point. @@ -937,9 +860,7 @@ def txdelay( return phase_time - def _calculate_tilt_element_delays( - self: _PhasedArrayMixinProtocol, - ) -> npt.NDArray[np.float_]: + def _calculate_tilt_element_delays(self) -> npt.NDArray[np.float_]: """ Calculate delays (in seconds) per array element to produce a given `tilt_angle`. @@ -951,7 +872,7 @@ def _calculate_tilt_element_delays( return delays def _calculate_focus_tilt_element_delays( - self: _PhasedArrayMixinProtocol, speed=1500.0 # m/s speed of sound in water + self, speed=1500.0 # m/s speed of sound in water ) -> npt.NDArray[np.float_]: """ Calculate delays (in seconds) per array element to focus the source. @@ -974,7 +895,7 @@ def _calculate_focus_tilt_element_delays( return delays def _set_element_delays( - self: _PhasedArrayMixinProtocol, + self, element_delays: npt.NDArray[np.float_] | None, ) -> npt.NDArray[np.float_]: """ @@ -1011,7 +932,7 @@ def _set_element_delays( return delays -class PhasedArraySource2D(PhasedArrayMixin, Source): +class PhasedArraySource2D(PhasedArraySource): """A phased array source in 2D. This source is shaped like a multiple segments in a line. Each segment can emit @@ -1111,7 +1032,7 @@ def calculate_waveform_scale(self, dx: float) -> float: return grid_point_density / source_density -class PhasedArraySource3D(PhasedArrayMixin, Source): +class PhasedArraySource3D(PhasedArraySource): """A linear phased array source in 3D. This source is shaped like a multiple rectangular segments in a line. Each segment diff --git a/tests/neurotechdevkit/test_sources.py b/tests/neurotechdevkit/test_sources.py index c3bcb700..4031a155 100644 --- a/tests/neurotechdevkit/test_sources.py +++ b/tests/neurotechdevkit/test_sources.py @@ -835,75 +835,87 @@ def dense_source(): def test_num_points_property(self): """Verify that .num_points matches the value received in the constructor.""" - source = self.create_test_source(num_points=10 * 13, num_elements=13) + source = TestPhasedArraySource2D.create_test_source( + num_points=10 * 13, num_elements=13 + ) assert source.num_points == 130 def test_point_source_delays_property_has_correct_shape(self): """Verify that .point_source_delays have the correct shape.""" - source = self.create_test_source(num_points=3, num_elements=3) + source = TestPhasedArraySource2D.create_test_source( + num_points=3, num_elements=3 + ) assert source.point_source_delays.shape[0] == 3 def test_aperture_property(self): """Verify that .aperture is constructed correctly. Note that for PhasedArraySource2D aperture must be computed. """ - source = self.create_test_source(pitch=1, num_elements=5, element_width=0.75) + source = TestPhasedArraySource2D.create_test_source( + pitch=1, num_elements=5, element_width=0.75 + ) # spacing = pitch - element_width # aperture = pitch * num_elements - spacing assert source.aperture == 4.75 def test_pitch_property(self): """Verify that .pitch matches the value received in the constructor.""" - source = self.create_test_source(pitch=0.01) + source = TestPhasedArraySource2D.create_test_source(pitch=0.01) assert source.pitch == 0.01 def test_num_elements_property(self): """Verify that .num_elements matches the value received in the constructor.""" warnings.simplefilter("ignore") - source = self.create_test_source(num_elements=12) + source = TestPhasedArraySource2D.create_test_source(num_elements=12) assert source.num_elements == 12 def test_num_elements_raises_error(self): """Verify that value error is raised if num elements is one.""" with pytest.raises(ValueError): - self.create_test_source(num_elements=1) + TestPhasedArraySource2D.create_test_source(num_elements=1) def test_tilt_angle_property(self): """Verify that .tilt_angle matches the value received in the constructor.""" - source = self.create_test_source(tilt_angle=12.34) + source = TestPhasedArraySource2D.create_test_source(tilt_angle=12.34) assert source.tilt_angle == 12.34 def test_spacing_property(self): """Verify that .spacing is set up correctly.""" - source = self.create_test_source(pitch=0.1123, element_width=0.100) + source = TestPhasedArraySource2D.create_test_source( + pitch=0.1123, element_width=0.100 + ) np.testing.assert_allclose(source.spacing, 0.0123) def test_focal_length_property(self): """Verify that .focal_length is set up correctly.""" - source = self.create_test_source(focal_length=0.123) + source = TestPhasedArraySource2D.create_test_source(focal_length=0.123) assert source.focal_length == 0.123 def test_validate_num_points_warns_user(self): """Verify that the user is warned when the number of points is adjusted.""" with pytest.warns(UserWarning): - self.create_test_source(num_points=13, num_elements=5) + TestPhasedArraySource2D.create_test_source(num_points=13, num_elements=5) def test_validate_num_points_modify_value(self): """Verify that the number of points are truncated when needed.""" warnings.simplefilter("ignore") - source = self.create_test_source(num_points=101, num_elements=10) + source = TestPhasedArraySource2D.create_test_source( + num_points=101, num_elements=10 + ) assert source.num_points == 100 def test_validate_num_points_does_not_modify_value(self): """Verify that the number of points is not modified if not required.""" - source = self.create_test_source(num_points=100, num_elements=10) + source = TestPhasedArraySource2D.create_test_source( + num_points=100, num_elements=10 + ) assert source.num_points == 100 def test_validate_num_points_does_not_warns(self): """Verify that no warning is shown when no modification is required.""" with warnings.catch_warnings(): warnings.simplefilter("error") - self.create_test_source(num_points=100, num_elements=10) + TestPhasedArraySource2D.create_test_source(num_points=100, num_elements=10) @pytest.mark.parametrize( "position, direction, focal_length, angle", @@ -925,7 +937,7 @@ def test_focal_point_is_computed_correctly( Test 3: displace in Y axis as a result from the negative unit direction. Test 4: Direction with x and y non-zero components. """ - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( position=position, direction=direction, focal_length=focal_length, @@ -935,7 +947,7 @@ def test_focal_point_is_computed_correctly( def test_focal_point_is_inf_for_unfocused_arrays(self): """Verify that focal point is (inf,inf) when unfocused.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( focal_length=np.inf, ) assert all([np.isinf(x) for x in source.focal_point]) @@ -946,7 +958,7 @@ def test_focal_point_is_inf_for_unfocused_arrays(self): ) def test_point_source_delays_property(self, tilt_angle, delay, expected_delays): """Verify that the original .point_source_delays are set correctly.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( tilt_angle=tilt_angle, num_points=2, num_elements=2, @@ -959,19 +971,23 @@ def test_point_source_delays_property(self, tilt_angle, delay, expected_delays): def test_point_source_delays_property_positive_delay(self): """Verify that .point_source_delays are set up correctly in the constructor.""" - source = self.create_test_source(num_points=10, delay=1, tilt_angle=0) + source = TestPhasedArraySource2D.create_test_source( + num_points=10, delay=1, tilt_angle=0 + ) np.testing.assert_allclose(source.point_source_delays, np.ones(shape=10)) def test_element_delays_property(self): """Verify that .element_delays property is set up correctly""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_points=10, num_elements=5, tilt_angle=0, element_delays=np.arange(0, 5) ) np.testing.assert_array_equal(source.element_delays, np.array([0, 1, 2, 3, 4])) def test_coordinates_has_correct_shape(self): """Verify that .coordinates has the expected shape.""" - source = self.create_test_source(num_points=1200, num_elements=10) + source = TestPhasedArraySource2D.create_test_source( + num_points=1200, num_elements=10 + ) assert source.coordinates.shape == (1200, 2) def test_coordinates_position(self, dense_source): @@ -1012,7 +1028,7 @@ def test_calculate_waveform_scale(self): length = num_elements * pitch - spacing num_points = 1000 dx = 0.01 - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_points=num_points, num_elements=5, pitch=0.01, element_width=0.009 ) scale = source.calculate_waveform_scale(dx=dx) @@ -1026,7 +1042,7 @@ def test_distribute_points_per_line(self): num_points = 52 expected_points_per_el = 10 warnings.simplefilter("ignore") - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_points=num_points, num_elements=num_elements ) pm = source._distribute_points_in_elements(num_elements, num_points) @@ -1037,7 +1053,7 @@ def test_distribute_points_per_line_correct_last_index(self): """Verify that the last index matches `num_points`.""" num_points = 10 num_elements = 5 - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_points=num_points, num_elements=num_elements ) pm = source._distribute_points_in_elements(num_elements, num_points) @@ -1045,7 +1061,7 @@ def test_distribute_points_per_line_correct_last_index(self): def test_element_positions_property(self): """Verify that .elements_positions has the expected shape and values""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=3, num_points=12, position=np.array((1.0, 2.0)), @@ -1063,7 +1079,7 @@ def test_distribute_points_per_line_even_distribution(self): num_points = 50 num_elements = 5 expected_points_per_el = 10 - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_points=num_points, num_elements=num_elements ) pm = source._distribute_points_in_elements(num_elements, num_points) @@ -1082,7 +1098,7 @@ def test_txdelay(self, tilt_angle, expected_delay): def test_calculate_focus_tilt_elements_delays_no_tilt(self): """Verify that delays are defined correctly only focusing.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=3, num_points=3, pitch=1, @@ -1097,7 +1113,7 @@ def test_calculate_focus_tilt_elements_delays_no_tilt(self): def test_calculate_focus_tilt_point_source_delays_tilt_and_focus(self): """Verify that delays are defined correctly only focusing.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=3, num_points=3, pitch=1, @@ -1119,7 +1135,7 @@ def test_calculate_point_source_delays_positive_delays( self, tilt_angle, focal_length ): """Verify that all delays are positive.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( tilt_angle=tilt_angle, focal_length=focal_length ) assert np.min(source.point_source_delays) >= 0 @@ -1127,29 +1143,31 @@ def test_calculate_point_source_delays_positive_delays( def test_calculate_point_source_delays_tilt_time_order(self): """Verify that point delays have the correct order.""" # positive angles, correct orientation - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=5, direction=np.array([1.0, 0]), tilt_angle=30.0 ) assert source.point_source_delays[0] < source.point_source_delays[-1] # negative angles, correct orientation - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=5, direction=np.array([1.0, 0]), tilt_angle=-30.0 ) assert source.point_source_delays[0] > source.point_source_delays[-1] # bottom source, shouldn't change behavior. - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=5, direction=np.array([-1.0, 0]), tilt_angle=-30.0 ) assert source.point_source_delays[0] > source.point_source_delays[-1] def test_calculate_point_source_delays_focus_non_focused(self): """Verify that delays are zero when the array should not focus.""" - source = self.create_test_source(focal_length=np.inf, tilt_angle=0) + source = TestPhasedArraySource2D.create_test_source( + focal_length=np.inf, tilt_angle=0 + ) assert all(np.isclose(source.point_source_delays, 0.0)) def test_calculate_point_source_delays_focus_are_symmetric_even(self): """Verify that delays are symmetric when focusing without tilt.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=10, focal_length=0.02, tilt_angle=0, @@ -1159,7 +1177,7 @@ def test_calculate_point_source_delays_focus_are_symmetric_even(self): def test_calculate_point_source_delays_focus_are_symmetric_odd(self): """Verify that delays are symmetric when focusing without tilt.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=5, focal_length=0.02, tilt_angle=0, @@ -1173,12 +1191,12 @@ def test_calculate_point_source_delays_focus_symmetrical_and_right_order(self): The test creates two sources with opposite tilt angle. The obtained delays should be identical, except that in reverse order. """ - source1 = self.create_test_source( + source1 = TestPhasedArraySource2D.create_test_source( num_elements=5, focal_length=0.02, tilt_angle=45, ) - source2 = self.create_test_source( + source2 = TestPhasedArraySource2D.create_test_source( num_elements=5, focal_length=0.02, tilt_angle=-45, @@ -1199,7 +1217,7 @@ def test_calculate_point_source_delays_focus_symmetrical_and_right_order(self): def test_calculate_point_source_delays_focus_distance_consistency(self): """Verify that delays have the expected order.""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=10, num_points=10, focal_length=1.0, @@ -1232,7 +1250,7 @@ def test_rotate(self, unit_direction, expected): def test_set_element_delays_is_none(self): """Verify that None `element_delays` is translated to zeros""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=5, tilt_angle=0.0, element_delays=None ) np.testing.assert_allclose( @@ -1241,7 +1259,7 @@ def test_set_element_delays_is_none(self): def test_set_element_delays_returns_array(self): """Verify that `element_delays` is a 1D numpy array with shape `num_elements`""" - source = self.create_test_source( + source = TestPhasedArraySource2D.create_test_source( num_elements=10, tilt_angle=0, element_delays=list(range(0, 10)), @@ -1251,7 +1269,9 @@ def test_set_element_delays_returns_array(self): def test_broadcast_delays_returns_correct_shape(self): """Verify that broadcast_delays returns the right shape and distribution.""" - source = self.create_test_source(num_elements=2, num_points=10) + source = TestPhasedArraySource2D.create_test_source( + num_elements=2, num_points=10 + ) point_source_delays = source._broadcast_delays(np.array([0.1, 0.2])) assert point_source_delays.shape == (10,) assert all(point_source_delays[0:5] == 0.1) From 7423855d67379658ef68e62dac7e03b001399610 Mon Sep 17 00:00:00 2001 From: d-lucena Date: Wed, 17 May 2023 13:37:33 +0000 Subject: [PATCH 058/198] Poetry version updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d16f3d3f..21dc7d9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "neurotechdevkit" -version = "v0.0.31" +version = "v0.0.32" description = "Neurotech Development Kit: an open-source software library designed to enhance accessibility to cutting-edge neurotechnology" authors = ["AE Studio "] maintainers = ["AE Studio "] From fd98f76f5f988e0913308b63efe0432d0f07e440 Mon Sep 17 00:00:00 2001 From: Newton Alex Sander Date: Wed, 17 May 2023 11:04:01 -0300 Subject: [PATCH 059/198] Printing a warning when the DEVITO_ARCH environment variable was not set (#30) * Printing a warning when the DEVITO_ARCH environment variable was not set * update warning message --------- Co-authored-by: Diogo de Lucena <90583560+d-lucena@users.noreply.github.com> --- Dockerfile | 1 + src/neurotechdevkit/__init__.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Dockerfile b/Dockerfile index aef4ceee..924afe75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ COPY . /app RUN rm -rf /app/.git ENV PATH "/venv/bin:$PATH" +ENV DEVITO_ARCH "gcc" LABEL org.opencontainers.image.source="https://github.com/agencyenterprise/neurotechdevkit" diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index ae9e5103..f305b388 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -1,6 +1,8 @@ """Main package for the neurotechdevkit.""" from __future__ import annotations +import os + from . import scenarios from .scenarios import load_result_from_disk @@ -11,6 +13,12 @@ "load_result_from_disk", ] +if "DEVITO_ARCH" not in os.environ: + print( + "WARNING: DEVITO_ARCH environment variable not set " + "and might cause compilation errors. See NDK documentation for help." + ) + class ScenarioNotFoundError(Exception): """Exception raised when a scenario is not found.""" From 7d9617ada834a4a2caeef8717526fefe3e392313 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 08:46:15 -0300 Subject: [PATCH 060/198] Fixing docstrings --- src/neurotechdevkit/scenarios/_results.py | 137 +++++++++++----------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index e59f4531..124c25f5 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -26,16 +26,16 @@ class Result(abc.ABC): This class should not be instantiated, use SteadyStateResult2D, SteadyStateResult3D, PulsedResult2D, or PulsedResult3D. - Attributes: - scenario: The scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenarios.Scenario): The scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording undersampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: an array containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ scenario: scenarios.Scenario @@ -95,16 +95,16 @@ class SteadyStateResult(Result): This class should not be instantiated, use SteadyStateResult2D or SteadyStateResult3D. - Attributes: - scenario: The scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenario.Scenario): The scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording undersampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: an array containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ steady_state: npt.NDArray[np.float_] | None = None @@ -181,19 +181,18 @@ def _generate_save_data(self) -> dict: class SteadyStateResult2D(SteadyStateResult): """A container for holding the results of a 2D steady-state simulation. - Attributes: - scenario: The 2D scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenarios.Scenario2D): The 2D scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording downsampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: a 3 dimensional array (two axes for space and one for time) + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): a 3 dimensional array (two axes for space and one for time) containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ - scenario: scenarios.Scenario2D def render_steady_state_amplitudes( @@ -263,17 +262,17 @@ def render_steady_state_amplitudes( class SteadyStateResult3D(SteadyStateResult): """A container for holding the results of a 3D steady-state simulation. - Attributes: - scenario: The 3D scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenarios.Scenario3D): The 3D scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording downsampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: a 4 dimensional array (three axes for space and one for time) + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): a 4 dimensional array (three axes for space and one for time) containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ scenario: scenarios.Scenario3D @@ -439,16 +438,16 @@ class PulsedResult(Result): This class should not be instantiated, use PulsedResult2D or PulsedResult3D. - Attributes: - scenario: The scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenario.Scenario): The scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording undersampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: an array containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ recorded_slice: tuple[int, float] | None = None @@ -548,17 +547,17 @@ def _generate_save_data(self) -> dict: class PulsedResult2D(PulsedResult): """A container for holding the results of a 2D pulsed simulation. - Attributes: - scenario: The 2D scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenarios.Scenario2D): The 2D scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording downsampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: a 3 dimensional array (two axes for space and one for time) + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): a 3 dimensional array (two axes for space and one for time) containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ scenario: scenarios.Scenario2D @@ -763,17 +762,17 @@ def _build_animation( class PulsedResult3D(PulsedResult): """A container for holding the results of a 3D pulsed simulation. - Attributes: - scenario: The 3D scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + Args: + scenario (scenarios.Scenario3D): The 3D scenario from which this result came. + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording downsampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: a 4 dimensional array (three axes for space and one for time) + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): a 4 dimensional array (three axes for space and one for time) containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + traces (stride.Traces): the stride Traces object returned from executing the pde. """ scenario: scenarios.Scenario3D @@ -1070,14 +1069,14 @@ def create_steady_state_result( Args: scenario: The scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording downsampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: an array containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing the pde. Raises: ValueError: if the ndim of the wavefield is less than 3 or more than 4. @@ -1131,14 +1130,14 @@ def create_pulsed_result( Args: scenario: The scenario from which this result came. - center_frequency: the center frequency (in hertz) of the sources. - effective_dt: the effective time step (in seconds) along the time axis of the + center_frequency (float): the center frequency (in hertz) of the sources. + effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording downsampling factor is larger than 1. - pde: the stride Operator that was executed to run the simulation. - shot: the stride Shot which was used for the simulation. - wavefield: an array containing the resulting simulation data. - traces: the stride Traces object returned from executing the pde. + pde (stride.Operator): the stride Operator that was executed to run the simulation. + shot (stride.Shot): the stride Shot which was used for the simulation. + wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing the pde. Raises: ValueError: if the ndim of the wavefield is less than 3 or more than 4. From 3146e9805b9dfdf93b58598b0fea2dbc8d174e25 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Tue, 9 May 2023 14:03:19 -0300 Subject: [PATCH 061/198] Linting --- src/neurotechdevkit/scenarios/_results.py | 148 +++++++++++++--------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index 124c25f5..ed41e3b2 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -29,13 +29,16 @@ class Result(abc.ABC): Args: scenario (scenarios.Scenario): The scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording + effective_dt (float): the effective time step (in seconds) along the time axis + of the wavefield. This can differ from the simulation dt if the recording undersampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + pde (stride.Operator): the stride Operator that was executed to run the + simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation + data. + traces (stride.Traces): the stride Traces object returned from executing the + pde. """ scenario: scenarios.Scenario @@ -98,13 +101,16 @@ class SteadyStateResult(Result): Args: scenario (scenario.Scenario): The scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording + effective_dt (float): the effective time step (in seconds) along the time axis + of the wavefield. This can differ from the simulation dt if the recording undersampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + pde (stride.Operator): the stride Operator that was executed to run the + simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): an array containing the resulting + simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. """ steady_state: npt.NDArray[np.float_] | None = None @@ -182,17 +188,21 @@ class SteadyStateResult2D(SteadyStateResult): """A container for holding the results of a 2D steady-state simulation. Args: - scenario (scenarios.Scenario2D): The 2D scenario from which this result came. + scenario (scenarios.Scenario2D): The 2D scenario from which this result + came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - downsampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt + if the recording downsampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run + the simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): a 3 dimensional array (two axes for space and one for time) - containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): a 3 dimensional array (two axes for + space and one for time) containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. """ + scenario: scenarios.Scenario2D def render_steady_state_amplitudes( @@ -263,16 +273,19 @@ class SteadyStateResult3D(SteadyStateResult): """A container for holding the results of a 3D steady-state simulation. Args: - scenario (scenarios.Scenario3D): The 3D scenario from which this result came. + scenario (scenarios.Scenario3D): The 3D scenario from which this result + came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - downsampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt + if the recording downsampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run the + simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): a 4 dimensional array (three axes for space and one for time) - containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): a 4 dimensional array (three axes for + space and one for time) containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. """ scenario: scenarios.Scenario3D @@ -441,13 +454,16 @@ class PulsedResult(Result): Args: scenario (scenario.Scenario): The scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - undersampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt + if the recording undersampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run + the simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): an array containing the resulting + simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. """ recorded_slice: tuple[int, float] | None = None @@ -548,16 +564,19 @@ class PulsedResult2D(PulsedResult): """A container for holding the results of a 2D pulsed simulation. Args: - scenario (scenarios.Scenario2D): The 2D scenario from which this result came. + scenario (scenarios.Scenario2D): The 2D scenario from which this + result came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - downsampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt + if the recording downsampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run the + simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): a 3 dimensional array (two axes for space and one for time) - containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): a 3 dimensional array (two axes for + space and one for time) containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. """ scenario: scenarios.Scenario2D @@ -763,16 +782,19 @@ class PulsedResult3D(PulsedResult): """A container for holding the results of a 3D pulsed simulation. Args: - scenario (scenarios.Scenario3D): The 3D scenario from which this result came. + scenario (scenarios.Scenario3D): The 3D scenario from which this result + came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - downsampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt + if the recording downsampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run the + simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): a 4 dimensional array (three axes for space and one for time) - containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): a 4 dimensional array (three axes for + space and one for time) containing the resulting simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. """ scenario: scenarios.Scenario3D @@ -1070,13 +1092,16 @@ def create_steady_state_result( Args: scenario: The scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - downsampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt if + the recording downsampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run + the simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): an array containing the resulting + simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. Raises: ValueError: if the ndim of the wavefield is less than 3 or more than 4. @@ -1131,13 +1156,16 @@ def create_pulsed_result( Args: scenario: The scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. - effective_dt (float): the effective time step (in seconds) along the time axis of the - wavefield. This can differ from the simulation dt if the recording - downsampling factor is larger than 1. - pde (stride.Operator): the stride Operator that was executed to run the simulation. + effective_dt (float): the effective time step (in seconds) along the + time axis of the wavefield. This can differ from the simulation dt if + the recording downsampling factor is larger than 1. + pde (stride.Operator): the stride Operator that was executed to run the + simulation. shot (stride.Shot): the stride Shot which was used for the simulation. - wavefield (npt.NDArray[np.float_]): an array containing the resulting simulation data. - traces (stride.Traces): the stride Traces object returned from executing the pde. + wavefield (npt.NDArray[np.float_]): an array containing the resulting + simulation data. + traces (stride.Traces): the stride Traces object returned from executing + the pde. Raises: ValueError: if the ndim of the wavefield is less than 3 or more than 4. From e29518e486cb76ee8d47110429346c160fc331ca Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 08:45:24 -0300 Subject: [PATCH 062/198] Small fixes to API rendering --- docs/api/scenarios.md | 15 +++++++------- mkdocs.yml | 3 +++ src/neurotechdevkit/__init__.py | 4 ++-- src/neurotechdevkit/scenarios/_base.py | 27 +++++++++++++------------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/docs/api/scenarios.md b/docs/api/scenarios.md index 68668147..8a9c8a17 100644 --- a/docs/api/scenarios.md +++ b/docs/api/scenarios.md @@ -3,13 +3,14 @@ ::: neurotechdevkit.scenarios.Scenario options: show_root_heading: true - members: - - __init__ - - get_layer_mask - - get_field_data - - add_source - - simulate_steady_state - - simulate_pulse + +::: neurotechdevkit.scenarios.Scenario2D + options: + show_root_heading: true + +::: neurotechdevkit.scenarios.Scenario3D + options: + show_root_heading: true ::: neurotechdevkit.scenarios.Scenario0 options: diff --git a/mkdocs.yml b/mkdocs.yml index 6e42fb9d..a57f4038 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,4 +68,7 @@ plugins: handlers: python: options: + docstring_options: + ignore_init_summary: false show_source: false + # merge_init_into_class: false \ No newline at end of file diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index f305b388..91ce541d 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -26,7 +26,7 @@ class ScenarioNotFoundError(Exception): pass -def make(scenario_id, complexity="fast"): +def make(scenario_id, complexity="fast") -> scenarios.Scenario: """ Initialize a scenario and return an object which represents the simulation. @@ -50,7 +50,7 @@ def make(scenario_id, complexity="fast"): ScenarioNotFoundError: Raised when the scenario id is not found. Returns: - scenarios.Scenario: an object representing the simulation. + An object representing the simulation. """ if scenario_id not in _scenario_map: raise ScenarioNotFoundError( diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index b8f01d05..b2094141 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -86,12 +86,13 @@ def complexity(self) -> str: Note: the only currently supported complexity is `fast`. Options are: - * `fast`: uses a small grid size (large grid spacing) so that simulations are - fast. - * `accurate`: uses a large grid size (small grid spacing) so that simulation - results are accurate. - * `balanced`: a grid size and grid spacing balanced between `fast` and - `accurate`. + + - `fast`: uses a small grid size (large grid spacing) so that simulations are + fast. + - `accurate`: uses a large grid size (small grid spacing) so that simulation + results are accurate. + - `balanced`: a grid size and grid spacing balanced between `fast` and + `accurate`. """ return self._complexity @@ -215,11 +216,11 @@ def target_radius(self) -> float: def materials(self) -> Mapping[str, Struct]: """A map between material name and material properties. - vp: the speed of sound (in m/s). - rho: the mass density (in kg/m³). - alpha: the absorption (in dB/cm). - render_color: the color used when rendering this material in the scenario layout - plot. + - vp: the speed of sound (in m/s). + - rho: the mass density (in kg/m³). + - alpha: the absorption (in dB/cm). + - render_color: the color used when rendering this material in the + scenario layout plot. """ return {name: material for name, material in self._material_layers} @@ -230,7 +231,7 @@ def layer_ids(self) -> Mapping[str, int]: @property def ordered_layers(self) -> list[str]: - """An list of material names in order of their layer id.""" + """A list of material names in order of their layer id.""" return [name for name, _ in self._material_layers] @property @@ -270,7 +271,7 @@ def get_layer_mask(self, layer_name: str) -> npt.NDArray[np.bool_]: """Return the mask for the desired layer. The mask is `True` at each gridpoint where the requested layer exists, - and False elsewhere. + and `False` elsewhere. Args: layer_name: The name of the desired layer. From eb5573ea1ec89515e813570261a0a681ac784c1f Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 08:58:52 -0300 Subject: [PATCH 063/198] Adapting docstrings to be correctly rendered in the API docs --- docs/api/make.md | 5 ++- mkdocs.yml | 4 +-- src/neurotechdevkit/__init__.py | 2 +- src/neurotechdevkit/scenarios/_base.py | 23 ++++++-------- src/neurotechdevkit/scenarios/_results.py | 12 +++---- src/neurotechdevkit/scenarios/_scenario_1.py | 33 +++++++++++--------- src/neurotechdevkit/scenarios/_scenario_2.py | 33 +++++++++++--------- 7 files changed, 54 insertions(+), 58 deletions(-) diff --git a/docs/api/make.md b/docs/api/make.md index e001a0eb..46c3745c 100644 --- a/docs/api/make.md +++ b/docs/api/make.md @@ -1,6 +1,5 @@ # NDK Interface -::: neurotechdevkit +::: neurotechdevkit.make options: - members: - - make + show_root_heading: true \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index a57f4038..650b160d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,7 +68,5 @@ plugins: handlers: python: options: - docstring_options: - ignore_init_summary: false show_source: false - # merge_init_into_class: false \ No newline at end of file + heading_level: 1 \ No newline at end of file diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index 91ce541d..737871ad 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -26,7 +26,7 @@ class ScenarioNotFoundError(Exception): pass -def make(scenario_id, complexity="fast") -> scenarios.Scenario: +def make(scenario_id: str, complexity: str = "fast") -> scenarios.Scenario: """ Initialize a scenario and return an object which represents the simulation. diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index b2094141..b29d1265 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -903,9 +903,7 @@ def render_layout( ) -> None: """Create a matplotlib figure showing the 2D scenario layout. - The grid can be turned on via: - - `plt.grid(True)` + The grid can be turned on via: `plt.grid(True)` Args: show_sources: whether or not to show the source transducer layer. @@ -1000,7 +998,6 @@ def get_target_mask(self) -> npt.NDArray[np.bool_]: center=self.target_center, radius=self.target_radius, ) - """Return the mask for the target region.""" return target_mask def simulate_pulse( @@ -1018,12 +1015,14 @@ def simulate_pulse( In this simulation, the sources will emit a pulse containing a few cycles of oscillation and then let the pulse propagate out to all edges of the scenario. - Note: the only supported frequency currently supported is 500kHz. Any other - value will raise a NotImplementedError. + !!! note + The only supported frequency currently supported is 500kHz. Any + other value will raise a NotImplementedError. - Warning: A poor choice of arguments to this function can lead to a failed - simulation. Make sure you understand the impact of supplying parameter values - other than the default if you chose to do so. + !!! warning + A poor choice of arguments to this function can lead to a failed + simulation. Make sure you understand the impact of supplying parameter + values other than the default if you chose to do so. Args: center_frequency: The center frequency (in hertz) to use for the @@ -1077,9 +1076,7 @@ def render_layout( needs to be specified via `slice_axis` and `slice_position`. Eg. to take a slice at z=0.01 m, use `slice_axis=2` and `slice_position=0.01`. - The grid can be turned on via: - - `plt.grid(True)` + The grid can be turned on via: `plt.grid(True)` Args: slice_axis: the axis along which to slice. If None, then the value returned @@ -1164,6 +1161,6 @@ def render_layout_3d( from different angles, zoom in our out, and turn layers on or off. See napari documentation for more information on the GUI: - https://napari.org/stable/tutorials/fundamentals/viewer.html + [documentation](https://napari.org/stable/tutorials/fundamentals/viewer.html) """ rendering.render_layout_3d_with_napari(self) diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index ed41e3b2..cad77215 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -213,9 +213,7 @@ def render_steady_state_amplitudes( ) -> None: """Create a matplotlib figure with the steady-state pressure wave amplitude. - The grid can be turned on via: - - `plt.grid(True)` + The grid can be turned on via: `plt.grid(True)` Args: show_sources: whether or not to show the source transducer layer. @@ -304,9 +302,7 @@ def render_steady_state_amplitudes( needs to be specified via `slice_axis` and `slice_position`. Eg. to take a slice at z=0.01 m, use `slice_axis=2` and `slice_position=0.01`. - The grid can be turned on via: - - `plt.grid(True)` + The grid can be turned on via: `plt.grid(True)` Args: slice_axis: the axis along which to slice. If None, then the value returned @@ -1163,7 +1159,7 @@ def create_pulsed_result( simulation. shot (stride.Shot): the stride Shot which was used for the simulation. wavefield (npt.NDArray[np.float_]): an array containing the resulting - simulation data. + simulation data. traces (stride.Traces): the stride Traces object returned from executing the pde. @@ -1208,7 +1204,7 @@ def load_result_from_disk(filepath: str | pathlib.Path) -> Result: This functionality is experimental, so do do not be surprised if you encounter issues calling this function. - Load a file that was saved to disk via Result.save_to_disk. + Load a file that was saved to disk via `Result.save_to_disk`. If the object saved in `filepath` is the result from a steady-state simulation the results will contain only the steady-state amplitudes. Instead, for pulsed diff --git a/src/neurotechdevkit/scenarios/_scenario_1.py b/src/neurotechdevkit/scenarios/_scenario_1.py index 9808070c..ff210a9b 100644 --- a/src/neurotechdevkit/scenarios/_scenario_1.py +++ b/src/neurotechdevkit/scenarios/_scenario_1.py @@ -43,11 +43,12 @@ class _Scenario1Mixin: Scenario 1 is based on benchmark 4 of the following paper: - Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems for - transcranial ultrasound simulation: Intercomparison of compressional wave models", - The Journal of the Acoustical Society of America 152, 1003 (2022); - doi: 10.1121/10.0013426 - https://asa.scitation.org/doi/pdf/10.1121/10.0013426 + Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems + for transcranial ultrasound simulation: Intercomparison of compressional wave + models", + The Journal of the Acoustical Society of America 152, 1003 (2022); + doi: 10.1121/10.0013426 + https://asa.scitation.org/doi/pdf/10.1121/10.0013426 """ @property @@ -103,11 +104,12 @@ class Scenario1_2D(_Scenario1Mixin, Scenario2D): Scenario 1 is based on benchmark 4 of the following paper: - Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems for - transcranial ultrasound simulation: Intercomparison of compressional wave models", - The Journal of the Acoustical Society of America 152, 1003 (2022); - doi: 10.1121/10.0013426 - https://asa.scitation.org/doi/pdf/10.1121/10.0013426 + Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems + for transcranial ultrasound simulation: Intercomparison of compressional wave + models", + The Journal of the Acoustical Society of America 152, 1003 (2022); + doi: 10.1121/10.0013426 + https://asa.scitation.org/doi/pdf/10.1121/10.0013426 """ _SCENARIO_ID = "scenario-1-2d-v0" @@ -150,11 +152,12 @@ class Scenario1_3D(_Scenario1Mixin, Scenario3D): Scenario 1 is based on benchmark 4 of the following paper: - Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems for - transcranial ultrasound simulation: Intercomparison of compressional wave models", - The Journal of the Acoustical Society of America 152, 1003 (2022); - doi: 10.1121/10.0013426 - https://asa.scitation.org/doi/pdf/10.1121/10.0013426 + Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems + for transcranial ultrasound simulation: Intercomparison of compressional wave + models", + The Journal of the Acoustical Society of America 152, 1003 (2022); + doi: 10.1121/10.0013426 + https://asa.scitation.org/doi/pdf/10.1121/10.0013426 """ _SCENARIO_ID = "scenario-1-3d-v0" diff --git a/src/neurotechdevkit/scenarios/_scenario_2.py b/src/neurotechdevkit/scenarios/_scenario_2.py index fc4078d4..f7ef4202 100644 --- a/src/neurotechdevkit/scenarios/_scenario_2.py +++ b/src/neurotechdevkit/scenarios/_scenario_2.py @@ -43,11 +43,12 @@ class _Scenario2Mixin: Scenario 2 is based on benchmark 8 of the following paper: - Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems for - transcranial ultrasound simulation: Intercomparison of compressional wave models", - The Journal of the Acoustical Society of America 152, 1003 (2022); - doi: 10.1121/10.0013426 - https://asa.scitation.org/doi/pdf/10.1121/10.0013426 + Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems + for transcranial ultrasound simulation: Intercomparison of compressional wave + models", + The Journal of the Acoustical Society of America 152, 1003 (2022); + doi: 10.1121/10.0013426 + https://asa.scitation.org/doi/pdf/10.1121/10.0013426 """ @property @@ -93,11 +94,12 @@ class Scenario2_2D(_Scenario2Mixin, Scenario2D): Scenario 2 is based on benchmark 8 of the following paper: - Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems for - transcranial ultrasound simulation: Intercomparison of compressional wave models", - The Journal of the Acoustical Society of America 152, 1003 (2022); - doi: 10.1121/10.0013426 - https://asa.scitation.org/doi/pdf/10.1121/10.0013426 + Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems + for transcranial ultrasound simulation: Intercomparison of compressional wave + models", + The Journal of the Acoustical Society of America 152, 1003 (2022); + doi: 10.1121/10.0013426 + https://asa.scitation.org/doi/pdf/10.1121/10.0013426 """ _SCENARIO_ID = "scenario-2-2d-v0" @@ -178,11 +180,12 @@ class Scenario2_3D(_Scenario2Mixin, Scenario3D): Scenario 2 is based on benchmark 8 of the following paper: - Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems for - transcranial ultrasound simulation: Intercomparison of compressional wave models", - The Journal of the Acoustical Society of America 152, 1003 (2022); - doi: 10.1121/10.0013426 - https://asa.scitation.org/doi/pdf/10.1121/10.0013426 + Jean-Francois Aubry, Oscar Bates, Christian Boehm, et al., "Benchmark problems + for transcranial ultrasound simulation: Intercomparison of compressional wave + models", + The Journal of the Acoustical Society of America 152, 1003 (2022); + doi: 10.1121/10.0013426 + https://asa.scitation.org/doi/pdf/10.1121/10.0013426 """ _SCENARIO_ID = "scenario-2-3d-v0" From b3817615707d5a6af087815b7081167f98afb15a Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 13:29:26 -0300 Subject: [PATCH 064/198] Skiping linting check --- src/neurotechdevkit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index 737871ad..3e9634e3 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -57,7 +57,7 @@ def make(scenario_id: str, complexity: str = "fast") -> scenarios.Scenario: f"Scenario '{scenario_id}' does not exist. Please refer to documentation" " for the list of provided scenarios." ) - return _scenario_map[scenario_id](complexity=complexity) + return _scenario_map[scenario_id](complexity=complexity) # type: ignore _scenario_map = { From d37a1279afcefc157e88229504c98960c3c9bd2c Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 13:41:53 -0300 Subject: [PATCH 065/198] Formatting link --- src/neurotechdevkit/sources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index e3212143..e595a5c7 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -158,8 +158,8 @@ class FocusedSource2D(Source): This source is shaped like an arc and has a circular focus. It is created by taking an arc of a circle and distributing point sources evenly along that arc. - See https://en.wikipedia.org/wiki/Circular_arc for relevant geometrical - calculations. + See [Circular arc](https://en.wikipedia.org/wiki/Circular_arc) for relevant + geometrical calculations. """ @property From da49e9cf3015c3934c0653a39c61bf91ec526969 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 14:09:10 -0300 Subject: [PATCH 066/198] Various fixes to API rendering --- docs/api/results.md | 9 +-------- mkdocs.yml | 3 +-- src/neurotechdevkit/scenarios/_metrics.py | 7 ++++--- src/neurotechdevkit/scenarios/_results.py | 7 ++++--- src/neurotechdevkit/sources.py | 10 ++++++---- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/docs/api/results.md b/docs/api/results.md index 4955e53b..f5ab1285 100644 --- a/docs/api/results.md +++ b/docs/api/results.md @@ -2,11 +2,4 @@ ::: neurotechdevkit.scenarios options: - members: - - create_steady_state_result - - create_pulsed_result - - load_result_from_disk - - SteadyStateResult2D - - SteadyStateResult3D - - PulsedResult2D - - PulsedResult3D \ No newline at end of file + filters: ["!Scenario", "!^_[^_]"] diff --git a/mkdocs.yml b/mkdocs.yml index 650b160d..8e2a143d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,5 +68,4 @@ plugins: handlers: python: options: - show_source: false - heading_level: 1 \ No newline at end of file + show_source: false \ No newline at end of file diff --git a/src/neurotechdevkit/scenarios/_metrics.py b/src/neurotechdevkit/scenarios/_metrics.py index 55deefaf..66a4d3f1 100644 --- a/src/neurotechdevkit/scenarios/_metrics.py +++ b/src/neurotechdevkit/scenarios/_metrics.py @@ -13,9 +13,10 @@ def calculate_all_metrics( The keys for the dictionary are the names of the metrics. The value for each metric is another dictionary containing the following: - value: The value of the metric. - unit-of-measurement: The unit of measurement for the metric. - description: A text description of the metric. + + - value: The value of the metric. + - unit-of-measurement: The unit of measurement for the metric. + - description: A text description of the metric. Args: result: the object containing the steady-state simulation results. diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index cad77215..4c515280 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -158,9 +158,10 @@ def metrics(self) -> dict[str, dict[str, str | float]]: The keys for the dictionary are the names of the metrics. The value for each metric is another dictionary containing the following: - value: The value of the metric. - unit-of-measurement: The unit of measurement for the metric. - description: A text description of the metric. + + - value: The value of the metric. + - unit-of-measurement: The unit of measurement for the metric. + - description: A text description of the metric. """ self.get_steady_state() return metrics.calculate_all_metrics(self) diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index e595a5c7..7e92d37a 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -241,8 +241,8 @@ class FocusedSource3D(Source): taking a section of a spherical shell and distributing source points over the surface. Points are distributed according to Fibonacci spirals. - See https://en.wikipedia.org/wiki/Spherical_cap for relevant geometrical - calculations. + See [Spherical cap](https://en.wikipedia.org/wiki/Spherical_cap) for relevant + geometrical calculations. """ def _calculate_coordinates(self) -> npt.NDArray[np.float_]: @@ -943,7 +943,8 @@ class PhasedArraySource2D(PhasedArraySource): If the number of points can not be evenly distributed in the number of elements, the remainder number of points from the even division will be discarded. - See https://en.wikipedia.org/wiki/Phased_array_ultrasonics for detailed explanation. + See [Phased array ultrasonics](https://en.wikipedia.org/wiki/Phased_array_ultrasonics) + for detailed explanation. """ @property @@ -1043,7 +1044,8 @@ class PhasedArraySource3D(PhasedArraySource): If the number of points can not be evenly distributed in the number of elements, the remainder number of points from the even division will be discarded. - See https://en.wikipedia.org/wiki/Phased_array_ultrasonics for detailed explanation. + See [Phased array ultrasonics](https://en.wikipedia.org/wiki/Phased_array_ultrasonics) + for detailed explanation. Args: position (npt.NDArray[np.float_]): a numpy float array in 3D indicating the From 08688b6090dd896a8896dff12865c879c83bd792 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 14:15:03 -0300 Subject: [PATCH 067/198] Linting --- src/neurotechdevkit/sources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index 7e92d37a..77e1b052 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -943,7 +943,7 @@ class PhasedArraySource2D(PhasedArraySource): If the number of points can not be evenly distributed in the number of elements, the remainder number of points from the even division will be discarded. - See [Phased array ultrasonics](https://en.wikipedia.org/wiki/Phased_array_ultrasonics) + See [Phased array ultras...](https://en.wikipedia.org/wiki/Phased_array_ultrasonics) for detailed explanation. """ @@ -1044,7 +1044,7 @@ class PhasedArraySource3D(PhasedArraySource): If the number of points can not be evenly distributed in the number of elements, the remainder number of points from the even division will be discarded. - See [Phased array ultrasonics](https://en.wikipedia.org/wiki/Phased_array_ultrasonics) + See [Phased array ultras...](https://en.wikipedia.org/wiki/Phased_array_ultrasonics) for detailed explanation. Args: From 42fe235b451092512fef3102171f97dd683c5595 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 15:36:40 -0300 Subject: [PATCH 068/198] Updating page title --- docs/api/sources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/sources.md b/docs/api/sources.md index f8564749..54115bc4 100644 --- a/docs/api/sources.md +++ b/docs/api/sources.md @@ -1,3 +1,3 @@ -# Results +# Sources ::: neurotechdevkit.sources From d6bccfc3058f451cf7485c4f372e26eae112700e Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 16:25:06 -0300 Subject: [PATCH 069/198] Adding custom css with identantion --- docs/css/mkdocstrings.css | 5 +++++ mkdocs.yml | 9 ++++++++- src/neurotechdevkit/scenarios/_base.py | 8 +++++--- src/neurotechdevkit/scenarios/_shots.py | 3 ++- 4 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 docs/css/mkdocstrings.css diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css new file mode 100644 index 00000000..6cfa8087 --- /dev/null +++ b/docs/css/mkdocstrings.css @@ -0,0 +1,5 @@ +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: .05rem solid var(--md-typeset-table-color); +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 8e2a143d..0daf5820 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,6 +10,7 @@ theme: - content.tabs.link - content.code.annotation - content.code.copy + language: en palette: - scheme: default @@ -41,6 +42,9 @@ nav: - Examples: generated/gallery - Contributing: contributing.md +extra_css: +- css/mkdocstrings.css + markdown_extensions: - pymdownx.highlight: anchor_linenums: true @@ -68,4 +72,7 @@ plugins: handlers: python: options: - show_source: false \ No newline at end of file + show_source: false + docstring_options: + ignore_init_summary: true + merge_init_into_class: true diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index b29d1265..df239141 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -83,7 +83,8 @@ def scenario_id(self) -> str: def complexity(self) -> str: """The complexity level to use when simulating this scenario. - Note: the only currently supported complexity is `fast`. + !!! note + The only currently supported complexity is `fast`. Options are: @@ -527,8 +528,9 @@ def _simulate_pulse( In this simulation, the sources will emit a pulse containing a few cycles of oscillation and then let the pulse propagate out to all edges of the scenario. - Note: the only supported frequency currently supported is 500kHz. Any other - value will raise a NotImplementedError. + !!! note + The only supported frequency currently supported is 500kHz. Any other + value will raise a NotImplementedError. Warning: A poor choice of arguments to this function can lead to a failed simulation. Make sure you understand the impact of supplying parameter values diff --git a/src/neurotechdevkit/scenarios/_shots.py b/src/neurotechdevkit/scenarios/_shots.py index 41575594..98d2492b 100644 --- a/src/neurotechdevkit/scenarios/_shots.py +++ b/src/neurotechdevkit/scenarios/_shots.py @@ -21,7 +21,8 @@ def create_shot( build the shot object, build the wavelet array for the shot, and then add the shot to the acquisitions for the problem. - Note: support for receivers in a shot is not currently implemented in ndk. + !!! note + Support for receivers in a shot is not currently implemented in ndk. Args: problem: the problem to which the shot will be be added. From b210d9bf077669744ccddb4be65a209e95213a61 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 10 May 2023 19:15:13 -0300 Subject: [PATCH 070/198] Removing module docstring to have it rendered in the API docs --- docs/api/results.md | 3 ++- docs/api/sources.md | 2 ++ src/neurotechdevkit/scenarios/__init__.py | 3 ++- src/neurotechdevkit/sources.py | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/api/results.md b/docs/api/results.md index f5ab1285..b4c43afe 100644 --- a/docs/api/results.md +++ b/docs/api/results.md @@ -2,4 +2,5 @@ ::: neurotechdevkit.scenarios options: - filters: ["!Scenario", "!^_[^_]"] + show_root_toc_entry: false + filters: ["!Scenario", "!^_[^_]"] \ No newline at end of file diff --git a/docs/api/sources.md b/docs/api/sources.md index 54115bc4..d702c729 100644 --- a/docs/api/sources.md +++ b/docs/api/sources.md @@ -1,3 +1,5 @@ # Sources ::: neurotechdevkit.sources + options: + show_root_toc_entry: false diff --git a/src/neurotechdevkit/scenarios/__init__.py b/src/neurotechdevkit/scenarios/__init__.py index 2da27981..b0eff17b 100644 --- a/src/neurotechdevkit/scenarios/__init__.py +++ b/src/neurotechdevkit/scenarios/__init__.py @@ -1,4 +1,5 @@ -"""All scenarios and results.""" +# noqa: D104 +# preventing package docstring to be rendered in documentation from . import materials from ._base import Scenario, Scenario2D, Scenario3D from ._results import ( diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index 77e1b052..d8a43764 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -1,4 +1,5 @@ -"""Sources module.""" +# noqa: D100 +# preventing package docstring to be rendered in documentation from __future__ import annotations import abc From 0c32fd7ee315bf8848e3657492c88091e7d088b6 Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Thu, 11 May 2023 08:38:32 -0300 Subject: [PATCH 071/198] All docstring args start with lower case, all return descriptions start with upper case --- src/neurotechdevkit/rendering/_formatting.py | 16 ++--- src/neurotechdevkit/rendering/_source.py | 20 +++--- src/neurotechdevkit/rendering/_target.py | 10 +-- src/neurotechdevkit/rendering/layers.py | 18 +++--- src/neurotechdevkit/rendering/layout.py | 20 +++--- src/neurotechdevkit/rendering/legends.py | 16 ++--- src/neurotechdevkit/rendering/napari.py | 26 ++++---- src/neurotechdevkit/rendering/simulations.py | 18 +++--- src/neurotechdevkit/scenarios/_base.py | 64 ++++++++++---------- src/neurotechdevkit/scenarios/_metrics.py | 22 +++---- src/neurotechdevkit/scenarios/_results.py | 44 +++++++------- src/neurotechdevkit/scenarios/_time.py | 2 +- src/neurotechdevkit/scenarios/_utils.py | 42 ++++++------- src/neurotechdevkit/sources.py | 10 +-- 14 files changed, 164 insertions(+), 164 deletions(-) diff --git a/src/neurotechdevkit/rendering/_formatting.py b/src/neurotechdevkit/rendering/_formatting.py index 567bd9a9..21ce0069 100644 --- a/src/neurotechdevkit/rendering/_formatting.py +++ b/src/neurotechdevkit/rendering/_formatting.py @@ -18,9 +18,9 @@ def configure_title( and axes dimensions for reproducibility. Args: - fig: The figure where the title should be configured. - title: The text for the title to give the plot. - x_pos: The x position in figure coordinates to pass into `fig.suptitle`. + fig: the figure where the title should be configured. + title: the text for the title to give the plot. + x_pos: the x position in figure coordinates to pass into `fig.suptitle`. """ fig.suptitle( title, @@ -36,9 +36,9 @@ def configure_axis_labels( """Configure the labels for the X and Y axes. Args: - ax: The axes containing the labels to be configured. - vertical_label: The label to apply to the vertical axis. - horizontal_label: The label to apply to the horizontal axis. + ax: the axes containing the labels to be configured. + vertical_label: the label to apply to the vertical axis. + horizontal_label: the label to apply to the horizontal axis. """ ax.set_xlabel( f"{horizontal_label} (m)", @@ -56,7 +56,7 @@ def configure_axis_ticks(ax: matplotlib.axes.Axes) -> None: """Configure the ticks and tick labels for the X and Y axes. Args: - ax: The axes containing the ticks to be configured. + ax: the axes containing the ticks to be configured. """ ax.tick_params(axis="x", rotation=45) for label in chain(ax.get_xticklabels(), ax.get_yticklabels()): @@ -67,7 +67,7 @@ def configure_grid(ax: matplotlib.axes.Axes) -> None: """Configure the grid for the plot. Args: - ax: The axes containing the grid to be configured. + ax: the axes containing the grid to be configured. """ ax.grid( color="#E7E7E7", diff --git a/src/neurotechdevkit/rendering/_source.py b/src/neurotechdevkit/rendering/_source.py index 00985812..7be8692d 100644 --- a/src/neurotechdevkit/rendering/_source.py +++ b/src/neurotechdevkit/rendering/_source.py @@ -54,7 +54,7 @@ def create_source_drawing_artist( coordinates. Args: - source_params: The SourceDrawingParams that describe the source. + source_params: the SourceDrawingParams that describe the source. transform: A Transform function which maps from plot data coordinates into display coordinates. @@ -98,9 +98,9 @@ def create_source_legend_artist( come from the offsetbox passed to a legend handler. Args: - loc: The location (in legend canvas coordinates) of the center of the canvas for + loc: the location (in legend canvas coordinates) of the center of the canvas for this icon. - width: The width (in legend canvas coordinates) of the canvas for this icon. + width: the width (in legend canvas coordinates) of the canvas for this icon. transform: A Transformation object that will translate from the legend handlebox to display coordinates. This should come from the handlebox passed to the legend handler. @@ -137,8 +137,8 @@ def _load_most_similar_source_image( """Load the source image which best matches the specified aperture and focus. Args: - aperture: The aperture of the source (in meters). - focal_length: The focal length of the source (in meters). For planar sources, + aperture: the aperture of the source (in meters). + focal_length: the focal length of the source (in meters). For planar sources, this value should equal np.inf. source_is_flat: A boolean indicating that the source should be represented flat. @@ -187,8 +187,8 @@ def _select_image_file( planar transducers, there is only one option. Args: - aperture: The aperture of the source (in meters). - focal_length: The focal length of the source (in meters). For planar sources, + aperture: the aperture of the source (in meters). + focal_length: the focal length of the source (in meters). For planar sources, this value should equal np.inf. source_is_flat: A boolean indicating if a source should be represented flat. @@ -209,8 +209,8 @@ def _choose_nearest(desired: float, options: list[int]) -> int: """Return the closest option to a desired value from a list of options. Args: - desired: The value for which we want the closest match. - options: The list of options to chose from. + desired: the value for which we want the closest match. + options: the list of options to chose from. Returns: The selected closest value. @@ -233,7 +233,7 @@ def _translate_and_rotate( direction. Args: - raw_img: The array containing the raw image data loaded from disk. + raw_img: the array containing the raw image data loaded from disk. direction: A vector indicating the direction that the transducer is aimed. Returns: diff --git a/src/neurotechdevkit/rendering/_target.py b/src/neurotechdevkit/rendering/_target.py index 959e5bf8..86c4cea3 100644 --- a/src/neurotechdevkit/rendering/_target.py +++ b/src/neurotechdevkit/rendering/_target.py @@ -22,9 +22,9 @@ def create_target_drawing_artist( on the plot, that can be `ax.transData`. Args: - target_loc: The location of the center of the target (in meters) in scenario + target_loc: the location of the center of the target (in meters) in scenario coordinates. - target_radius: The radius of the target (in meters). + target_radius: the radius of the target (in meters). transform: A Transform function which maps from plot data coordinates into display coordinates. @@ -58,9 +58,9 @@ def create_target_legend_artist( on the plot, that can be `ax.transData`. Args: - center_loc: The location of the center of the legend canvas (in canvas + center_loc: the location of the center of the legend canvas (in canvas coordinates). - target_radius: The radius of the target (in canvas coordinates). + target_radius: the radius of the target (in canvas coordinates). transform: A Transform function which maps from canvas coordinates into display coordinates. @@ -87,7 +87,7 @@ def _load_target(version: str) -> npt.NDArray: The array is returned in RGBA channel order. Args: - version: The version of the target symbol to load. Options are 'dark' or + version: the version of the target symbol to load. Options are 'dark' or 'light'. Raises: diff --git a/src/neurotechdevkit/rendering/layers.py b/src/neurotechdevkit/rendering/layers.py index f34b602e..43a74c21 100644 --- a/src/neurotechdevkit/rendering/layers.py +++ b/src/neurotechdevkit/rendering/layers.py @@ -13,8 +13,8 @@ def draw_source(ax: matplotlib.axes.Axes, source: SourceDrawingParams) -> None: This layer can be added to any scenario figure in 2D. Args: - ax: The axes to draw the sources on. - source: The Source object to be drawn. + ax: the axes to draw the sources on. + source: the Source object to be drawn. """ source_artist = create_source_drawing_artist(source, transform=ax.transData) ax.add_artist(source_artist) @@ -28,10 +28,10 @@ def draw_target( This layer can be added to any scenario figure in 2D. Args: - ax: The axes to draw the target on. + ax: the axes to draw the target on. target_loc: An array of shape (2,) indicating the location (in meters) of the target within the scenario. - target_radius: The radius (in meters) of the target. + target_radius: the radius (in meters) of the target. """ target_artist = create_target_drawing_artist( @@ -59,12 +59,12 @@ def draw_material_outlines( ideally there are no gaps between pixels. Args: - ax: The axes to draw the target on. + ax: the axes to draw the target on. material_field: A 2D array with the material id of the material at each gridpoint. - dx: The spacing (in meters) between gridpoints in the scenario. - origin: The 2D origin (in meters) of the scenario slice to be drawn. - upsample_factor: The factor to use when upsampling the material field before + dx: the spacing (in meters) between gridpoints in the scenario. + origin: the 2D origin (in meters) of the scenario slice to be drawn. + upsample_factor: the factor to use when upsampling the material field before detecting transitions between materials. If the factor is N, then each pixel will be split into N^2 pixels. Defaults to 1 (no resampling). """ @@ -104,7 +104,7 @@ def _get_outline_mask(field: npt.NDArray[np.int_]) -> npt.NDArray[np.bool_]: are superimposed to determine where to return white pixels. Args: - field: The input field from which outlines should be detected. + field: the input field from which outlines should be detected. Returns: A boolean mask marking the transition between materials in the input field. diff --git a/src/neurotechdevkit/rendering/layout.py b/src/neurotechdevkit/rendering/layout.py index 154e2622..7479df84 100644 --- a/src/neurotechdevkit/rendering/layout.py +++ b/src/neurotechdevkit/rendering/layout.py @@ -42,14 +42,14 @@ def create_layout_fig( the field along each axis. origin: An array of shape (2,) which contains the spatial coordinates (in meters) of the field element with indices (0, 0). - color_sequence: The list of colors which correspond to each material in the + color_sequence: the list of colors which correspond to each material in the field. field: A 2D integer array indicating which material is located at each point on the grid. Returns: - fig: The new matplotlib figure. - ax: The axes containing the plotted data. + fig: the new matplotlib figure. + ax: the axes containing the plotted data. """ assert len(extent) == 2, "The rendering only supports 2D fields." @@ -82,9 +82,9 @@ def configure_layout_plot( """Configure a layout plot figure including axes, title, and legend. Args: - fig: The layout plot figure to configure. - ax: The axes which contains the layout plot. - color_sequence: The list of colors which correspond to each material in the + fig: the layout plot figure to configure. + ax: the axes which contains the layout plot. + color_sequence: the list of colors which correspond to each material in the field. layer_labels: A list of labels to apply to the material layers in the plot. show_sources: Whether or not to include the source transducer layer in the @@ -94,9 +94,9 @@ def configure_layout_plot( the field along each axis. origin: An array of shape (2,) which contains the spatial coordinates (in meters) of the field element with indices (0, 0). - vertical_label: The label to apply to the vertical axis. - horizontal_label: The label to apply to the horizontal axis. - title: The title to give the figure. + vertical_label: the label to apply to the vertical axis. + horizontal_label: the label to apply to the horizontal axis. + title: the title to give the figure. """ configure_title(fig, title) _configure_legend(ax, layer_labels, color_sequence, show_target, show_sources) @@ -120,7 +120,7 @@ def _configure_legend( """Configure the legend for the figure. Args: - axes: The axes containing the legend to be configured. + axes: the axes containing the legend to be configured. layer_labels: A list of labels to give the material layers. color_sequence: A list of colors to give the material layers. show_target: Whether or not to show the target marker. diff --git a/src/neurotechdevkit/rendering/legends.py b/src/neurotechdevkit/rendering/legends.py index bcf5a9a5..5cbd8c60 100644 --- a/src/neurotechdevkit/rendering/legends.py +++ b/src/neurotechdevkit/rendering/legends.py @@ -99,11 +99,11 @@ def legend_artist( """Return the artist that draws the target in the legend. Args: - legend: The legend for which these legend artists are being created. - orig_handle: The object for which these legend artists are being created. - fontsize: The fontsize in pixels. The artists being created should be scaled + legend: the legend for which these legend artists are being created. + orig_handle: the object for which these legend artists are being created. + fontsize: the fontsize in pixels. The artists being created should be scaled according to the given fontsize. - handlebox: The box which has been created to hold this legend entry's + handlebox: the box which has been created to hold this legend entry's artists. Artists created in the legend_artist method must be added to this handlebox inside this method. @@ -156,11 +156,11 @@ def legend_artist( """Return the artist that draws the source in the legend. Args: - legend: The legend for which these legend artists are being created. - orig_handle: The object for which these legend artists are being created. - fontsize: The fontsize in pixels. The artists being created should be scaled + legend: the legend for which these legend artists are being created. + orig_handle: the object for which these legend artists are being created. + fontsize: the fontsize in pixels. The artists being created should be scaled according to the given fontsize. - handlebox: The box which has been created to hold this legend entry's + handlebox: the box which has been created to hold this legend entry's artists. Artists created in the legend_artist method must be added to this handlebox inside this method. diff --git a/src/neurotechdevkit/rendering/napari.py b/src/neurotechdevkit/rendering/napari.py index fc21dd0a..ea2f355c 100644 --- a/src/neurotechdevkit/rendering/napari.py +++ b/src/neurotechdevkit/rendering/napari.py @@ -63,8 +63,8 @@ class ViewerConfig3D(NamedTuple): """Configuration parameters for 3D visualization of scenarios. Attributes: - init_angles: The viewing angle to set on startup. - init_zoom: The zoom to set at startup. + init_angles: the viewing angle to set on startup. + init_zoom: the zoom to set at startup. colormaps: A map from layer names to the name of the colormap which should be used to display the layer. opacities: A map from layer names to the opacity that should be used for the @@ -81,7 +81,7 @@ def render_layout_3d_with_napari(scenario: "scenarios.Scenario3D") -> None: """Render the scenario layout in 3D using napari. Args: - scenario: The 3D scenario to be rendered. + scenario: the 3D scenario to be rendered. Raises: ImportError: If napari is not found. @@ -93,7 +93,7 @@ def render_amplitudes_3d_with_napari(result: "scenarios.SteadyStateResult3D") -> """Render the scenario layout in 3D using napari. Args: - scenario: The 3D scenario to be rendered. + scenario: the 3D scenario to be rendered. Raises: ImportError: If napari is not found. @@ -148,9 +148,9 @@ def add_material_layers( """Add the individual material layers as images to a napari Viewer. Args: - viewer: The napari Viewer to which the layers should be added. - scenario: The 3D scenario which is being visualized. - viewer_config: The configuration parameters for the 3d visualization. + viewer: the napari Viewer to which the layers should be added. + scenario: the 3D scenario which is being visualized. + viewer_config: the configuration parameters for the 3d visualization. """ colormaps = viewer_config.colormaps opacities = viewer_config.opacities @@ -173,7 +173,7 @@ def add_steady_state_amplitudes( """Add the steady-state amplitudes as an image layer to a napari Viewer. Args: - viewer: The napari Viewer to which the target should be added. + viewer: the napari Viewer to which the target should be added. amplitudes: A 3D numpy array containing the steady-state amplitudes. """ viewer.add_image( @@ -188,8 +188,8 @@ def add_target(viewer: _NapariViewer, scenario: "scenarios.Scenario3D") -> None: """Add the target as a shapes layer to a napari Viewer. Args: - viewer: The napari Viewer to which the target should be added. - scenario: The 3D scenario which is being visualized. + viewer: the napari Viewer to which the target should be added. + scenario: the 3D scenario which is being visualized. """ target_pos = ((scenario.target_center - scenario.origin) / scenario.dx).astype(int) target_rad = int(scenario.target_radius / scenario.dx) @@ -247,9 +247,9 @@ def add_source( Each individual point source is plotted as a point. Args: - viewer: The napari Viewer to which the target should be added. - scenario: The 3D scenario which is being visualized. - source: The source to be drawn. + viewer: the napari Viewer to which the target should be added. + scenario: the 3D scenario which is being visualized. + source: the source to be drawn. """ source_coords = ((source.coordinates - scenario.origin) / scenario.dx).astype(int) viewer.add_points( diff --git a/src/neurotechdevkit/rendering/simulations.py b/src/neurotechdevkit/rendering/simulations.py index 65defe9f..d4790cc1 100644 --- a/src/neurotechdevkit/rendering/simulations.py +++ b/src/neurotechdevkit/rendering/simulations.py @@ -87,7 +87,7 @@ def create_pulsed_figure( wavefield: An array in 2 spatial dimensions and one temporal dimension with the recorded pressures during the simulation. The temporal dimension should be the last one. - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. @@ -127,8 +127,8 @@ def configure_result_plot( Configuration includes: axes, title, colorbar, and legend. Args: - fig: The layout plot figure to configure. - ax: The axes which contains the layout plot. + fig: the layout plot figure to configure. + ax: the axes which contains the layout plot. show_sources: Whether or not to include the source transducer layer in the legend. show_target: Whether or not to include the the target layer in the legend. @@ -136,9 +136,9 @@ def configure_result_plot( the field along each axis. origin: An array of shape (2,) which contains the spatial coordinates (in meters) of the field element with indices (0, 0). - vertical_label: The label to apply to the vertical axis. - horizontal_label: The label to apply to the horizontal axis. - title: The title to give the figure. + vertical_label: the label to apply to the vertical axis. + horizontal_label: the label to apply to the horizontal axis. + title: the title to give the figure. clim: A tuple with (min, max) values for the limits for the colorbar. """ configure_title(fig, title, x_pos=0.63) @@ -166,8 +166,8 @@ def _configure_colorbar( """Configure the colorbar for the steady-state amplitude plot. Args: - fig: The figure containing the plot to be configured. - ax: The axes containing the plot to be configured. + fig: the figure containing the plot to be configured. + ax: the axes containing the plot to be configured. clim: A tuple with (min, max) values for the limits for the colorbar. """ im = ax.get_images()[0] @@ -186,7 +186,7 @@ def _configure_legend( """Configure the legend for the steady-state amplitude plot. Args: - ax: The axes containing the legend to be configured. + ax: the axes containing the legend to be configured. show_target: Whether or not to show the target marker. show_sources: Whether or not to show the source markers. """ diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index df239141..69c6e189 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -275,7 +275,7 @@ def get_layer_mask(self, layer_name: str) -> npt.NDArray[np.bool_]: and `False` elsewhere. Args: - layer_name: The name of the desired layer. + layer_name: the name of the desired layer. Raises: ValueError: if `layer_name` does not match the name of one of the existing @@ -329,7 +329,7 @@ def reset(self) -> None: def add_source(self, source: Source) -> None: """Add the specified source to the scenario. - Sources can also added or removed by modifying the Scenario.sources list. + Sources can also be added or removed by modifying the Scenario.sources list. Changes can only be made to sources before a simulation has started. @@ -381,22 +381,22 @@ def simulate_steady_state( values other than the default if you chose to do so. Args: - center_frequency: The center frequency (in hertz) to use for the + center_frequency: the center frequency (in hertz) to use for the continuous-wave source output. No other value besides 500kHz (the default) is currently supported. - points_per_period: The number of points in time to simulate for each cycle + points_per_period: the number of points in time to simulate for each cycle of the wave. - n_cycles_steady_state: The number of complete cycles to use when calculating + n_cycles_steady_state: the number of complete cycles to use when calculating the steady-state wave amplitudes. - time_to_steady_state: The amount of time (in seconds) the simulation should + time_to_steady_state: the amount of time (in seconds) the simulation should run before measuring the steady-state amplitude. If the value is None, this time will automatically be set to the amount of time it would take to propagate from one corner to the opposite and back in the medium with the slowest speed of sound in the scenario. - recording_time_undersampling: The undersampling factor to apply to the time + recording_time_undersampling: the undersampling factor to apply to the time axis when recording simulation results. One out of every this many consecutive time points will be recorded and all others will be dropped. - n_jobs: The number of threads to be used for the computation. Use None to + n_jobs: the number of threads to be used for the computation. Use None to leverage Devito automatic tuning. Raises: @@ -482,19 +482,19 @@ def simulate_pulse( values other than the default if you chose to do so. Args: - center_frequency: The center frequency (in hertz) to use for the + center_frequency: the center frequency (in hertz) to use for the continuous-wave source output. No other value besides 500kHz (the default) is currently supported. - points_per_period: The number of points in time to simulate for each cycle + points_per_period: the number of points in time to simulate for each cycle of the wave. - simulation_time: The amount of time (in seconds) the simulation should run. + simulation_time: the amount of time (in seconds) the simulation should run. If the value is None, this time will automatically be set to the amount of time it would take to propagate from one corner to the opposite in the medium with the slowest speed of sound in the scenario. - recording_time_undersampling: The undersampling factor to apply to the time + recording_time_undersampling: the undersampling factor to apply to the time axis when recording simulation results. One out of every this many consecutive time points will be recorded and all others will be dropped. - n_jobs: The number of threads to be used for the computation. Use None to + n_jobs: the number of threads to be used for the computation. Use None to leverage Devito automatic tuning. Raises: @@ -537,19 +537,19 @@ def _simulate_pulse( other than the default if you chose to do so. Args: - center_frequency: The center frequency (in hertz) to use for the + center_frequency: the center frequency (in hertz) to use for the continuous-wave source output. No other value besides 500kHz (the default) is currently supported. - points_per_period: The number of points in time to simulate for each cycle + points_per_period: the number of points in time to simulate for each cycle of the wave. - simulation_time: The amount of time (in seconds) the simulation should run. + simulation_time: the amount of time (in seconds) the simulation should run. If the value is None, this time will automatically be set to the amount of time it would take to propagate from one corner to the opposite in the medium with the slowest speed of sound in the scenario. - recording_time_undersampling: The undersampling factor to apply to the time + recording_time_undersampling: the undersampling factor to apply to the time axis when recording simulation results. One out of every this many consecutive time points will be recorded and all others will be dropped. - n_jobs: The number of threads to be used for the computation. Use None to + n_jobs: the number of threads to be used for the computation. Use None to leverage Devito automatic tuning. slice_axis: the axis along which to slice the 3D field to be recorded. If None, then the complete field will be recorded. Use 0 for X axis, 1 for @@ -632,7 +632,7 @@ def _setup_sub_problem( simulation_mode: the type of simulation which will be run. Returns: - the `SubProblem` to use for the simulation. + The `SubProblem` to use for the simulation. """ self._ensure_source() self._freeze_sources() @@ -653,7 +653,7 @@ def _setup_shot( simulation_mode: the type of simulation which will be run. Returns: - the `Shot` to use for the simulation. + The `Shot` to use for the simulation. """ problem = self.problem assert problem.grid.time is not None @@ -794,9 +794,9 @@ def _get_steady_state_recording_time_bounds( simulation. Args: - ppp: The number of points in time per phase to simulate for each cycle of + ppp: the number of points in time per phase to simulate for each cycle of the wave. - n_cycles: The number of complete cycles to record at the end of the + n_cycles: the number of complete cycles to record at the end of the simulation. Returns: @@ -833,15 +833,15 @@ def _execute_pde( """Execute the PDE for the simulation. Args: - pde: The `Operator` containing the PDE to execute. - sub_problem: The `SubProblem` containing details of the source and waveform + pde: the `Operator` containing the PDE to execute. + sub_problem: the `SubProblem` containing details of the source and waveform for the simulation. - save_bounds: The time indices bounding the period of time to be recorded. - save_undersampling: The undersampling factor to apply to the time axis when + save_bounds: the time indices bounding the period of time to be recorded. + save_undersampling: the undersampling factor to apply to the time axis when recording simulation results. wavefield_slice: A tuple of slices defining the region of the grid to record. - n_jobs: The number of threads to be used for the computation. Use None to + n_jobs: the number of threads to be used for the computation. Use None to leverage Devito automatic tuning. Returns: The `Traces` which are produced by the simulation. @@ -1027,19 +1027,19 @@ def simulate_pulse( values other than the default if you chose to do so. Args: - center_frequency: The center frequency (in hertz) to use for the + center_frequency: the center frequency (in hertz) to use for the continuous-wave source output. No other value besides 500kHz (the default) is currently supported. - points_per_period: The number of points in time to simulate for each cycle + points_per_period: the number of points in time to simulate for each cycle of the wave. - simulation_time: The amount of time (in seconds) the simulation should run. + simulation_time: the amount of time (in seconds) the simulation should run. If the value is None, this time will automatically be set to the amount of time it would take to propagate from one corner to the opposite in the medium with the slowest speed of sound in the scenario. - recording_time_undersampling: The undersampling factor to apply to the time + recording_time_undersampling: the undersampling factor to apply to the time axis when recording simulation results. One out of every this many consecutive time points will be recorded and all others will be dropped. - n_jobs: The number of threads to be used for the computation. Use None to + n_jobs: the number of threads to be used for the computation. Use None to leverage Devito automatic tuning. slice_axis: the axis along which to slice the 3D field to be recorded. If None, then the complete field will be recorded. Use 0 for X axis, 1 for diff --git a/src/neurotechdevkit/scenarios/_metrics.py b/src/neurotechdevkit/scenarios/_metrics.py index 66a4d3f1..63ec8813 100644 --- a/src/neurotechdevkit/scenarios/_metrics.py +++ b/src/neurotechdevkit/scenarios/_metrics.py @@ -14,8 +14,8 @@ def calculate_all_metrics( The keys for the dictionary are the names of the metrics. The value for each metric is another dictionary containing the following: - - value: The value of the metric. - - unit-of-measurement: The unit of measurement for the metric. + - value: the value of the metric. + - unit-of-measurement: the unit of measurement for the metric. - description: A text description of the metric. Args: @@ -127,8 +127,8 @@ def calculate_mechanical_index( center frequency. Args: - result: The Result object containing the simulation results. - layer: The layer within which to calculate the mechanical index. If None, the + result: the Result object containing the simulation results. + layer: the layer within which to calculate the mechanical index. If None, the default, the mechanical index is calculated over the entire simulation space. @@ -154,7 +154,7 @@ def calculate_focal_gain(result: results.SteadyStateResult) -> float: brain, presented in Decibels. Args: - result: The Result object containing the simulation results. + result: the Result object containing the simulation results. Returns: The focal gain (in dB) @@ -183,7 +183,7 @@ def calculate_i_ta(result: results.SteadyStateResult) -> npt.NDArray[np.float_]: is the center frequency. Args: - result: The Result object containing the simulation results. + result: the Result object containing the simulation results. Returns: the time-averaged intensity at each point in space (in W/m^2). @@ -205,7 +205,7 @@ def calculate_i_ta_target(result: results.SteadyStateResult) -> float: is the center frequency. Args: - result: The Result object containing the simulation results. + result: the Result object containing the simulation results. Returns: the time-averaged intensity averaged over the target region (in W/m^2). @@ -234,7 +234,7 @@ def calculate_i_ta_off_target(result: results.SteadyStateResult) -> float: The intensity is averaged over the brain region excluding the target region. Args: - result: The Result object containing the simulation results. + result: the Result object containing the simulation results. Returns: the time-averaged intensity averaged over the brain but outside of the target @@ -259,7 +259,7 @@ def calculate_i_pa_target(result: results.SteadyStateResult) -> float: See `calculate_i_ta_target` for more information about how the metric is calculated. Args: - result: The Result object containing the simulation results. + result: the Result object containing the simulation results. Returns: the pulse-averaged intensity averaged over the target region (in W/m^2). @@ -280,7 +280,7 @@ def calculate_i_pa_off_target(result: results.SteadyStateResult) -> float: calculated. Args: - result: The Result object containing the simulation results. + result: the Result object containing the simulation results. Returns: the pulse-averaged intensity averaged over the brain but outside of the target @@ -312,7 +312,7 @@ def convert(from_uom, to_uom, value): Args: from_uom: A string containing unit-of-measurement from which to convert. to_uom: A string containing unit-of-measurement to which to convert. - value: The value to be converted. + value: the value to be converted. Raises: ValueError: If an unsupported conversion is requested. diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/scenarios/_results.py index 4c515280..db9632c6 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/scenarios/_results.py @@ -27,7 +27,7 @@ class Result(abc.ABC): PulsedResult2D, or PulsedResult3D. Args: - scenario (scenarios.Scenario): The scenario from which this result came. + scenario (scenarios.Scenario): the scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording @@ -63,7 +63,7 @@ def save_to_disk(self, filepath: str | pathlib.Path) -> None: in 3D. Args: - filepath: The path to the file where the results should be exported. Usually + filepath: the path to the file where the results should be exported. Usually a .pkl.gz file. """ try: @@ -99,7 +99,7 @@ class SteadyStateResult(Result): SteadyStateResult3D. Args: - scenario (scenario.Scenario): The scenario from which this result came. + scenario (scenario.Scenario): the scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if the recording @@ -159,8 +159,8 @@ def metrics(self) -> dict[str, dict[str, str | float]]: The keys for the dictionary are the names of the metrics. The value for each metric is another dictionary containing the following: - - value: The value of the metric. - - unit-of-measurement: The unit of measurement for the metric. + - value: the value of the metric. + - unit-of-measurement: the unit of measurement for the metric. - description: A text description of the metric. """ self.get_steady_state() @@ -189,7 +189,7 @@ class SteadyStateResult2D(SteadyStateResult): """A container for holding the results of a 2D steady-state simulation. Args: - scenario (scenarios.Scenario2D): The 2D scenario from which this result + scenario (scenarios.Scenario2D): the 2D scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the @@ -272,7 +272,7 @@ class SteadyStateResult3D(SteadyStateResult): """A container for holding the results of a 3D steady-state simulation. Args: - scenario (scenarios.Scenario3D): The 3D scenario from which this result + scenario (scenarios.Scenario3D): the 3D scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the @@ -406,10 +406,10 @@ def _extract_steady_state_amplitude( need to integrate over integer number of cycles. Args: - data: The wave data to process. The array should contain 2 or 3 space dimensions + data: the wave data to process. The array should contain 2 or 3 space dimensions followed by the time dimension. - freq_hz: The frequency (in hertz) to extract from the FFT. - dt: The time axis spacing (in seconds). + freq_hz: the frequency (in hertz) to extract from the FFT. + dt: the time axis spacing (in seconds). by_slice: If False, the fft is executed over the entire data array at once, which is faster but memory intensive. If True, the fft is executed over the data slice by slice, which is slower but uses less memory. @@ -449,7 +449,7 @@ class PulsedResult(Result): This class should not be instantiated, use PulsedResult2D or PulsedResult3D. Args: - scenario (scenario.Scenario): The scenario from which this result came. + scenario (scenario.Scenario): the scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt @@ -561,7 +561,7 @@ class PulsedResult2D(PulsedResult): """A container for holding the results of a 2D pulsed simulation. Args: - scenario (scenarios.Scenario2D): The 2D scenario from which this + scenario (scenarios.Scenario2D): the 2D scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the @@ -603,7 +603,7 @@ def render_pulsed_simulation_animation( the animation. time_lim: the input time limit tuple to validate. The expected format is (minimum_time, maximum_time). - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. @@ -651,7 +651,7 @@ def create_video_file( the animation. time_lim: the input time limit tuple to validate. The expected format is (minimum_time, maximum_time). - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. fps: the frames per second in the animation. @@ -702,7 +702,7 @@ def _build_animation( show_target: whether or not to show the target layer. show_material_outlines: whether or not to display a thin white outline of the transition between different materials. - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. @@ -779,7 +779,7 @@ class PulsedResult3D(PulsedResult): """A container for holding the results of a 3D pulsed simulation. Args: - scenario (scenarios.Scenario3D): The 3D scenario from which this result + scenario (scenarios.Scenario3D): the 3D scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the @@ -855,7 +855,7 @@ def render_pulsed_simulation_animation( `scenario.get_default_slice_position()` is used. time_lim: the input time limit tuple to validate. The expected format is (minimum_time, maximum_time). - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. @@ -912,7 +912,7 @@ def create_video_file( `scenario.get_default_slice_position()` is used. time_lim: the input time limit tuple to validate. The expected format is (minimum_time, maximum_time). - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. fps: the frames per second in the animation. @@ -976,7 +976,7 @@ def _build_animation( show_target: whether or not to show the target layer. show_material_outlines: whether or not to display a thin white outline of the transition between different materials. - norm: The normalization method used to scale scalar data to the [0, 1] + norm: the normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap. For a list of available scales, call `matplotlib.scale.get_scale_names()`. @@ -1087,7 +1087,7 @@ def create_steady_state_result( has N-1 spatial dimensions and 1 time dimension. Args: - scenario: The scenario from which this result came. + scenario: the scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if @@ -1151,7 +1151,7 @@ def create_pulsed_result( spatial dimensions and 1 time dimension. Args: - scenario: The scenario from which this result came. + scenario: the scenario from which this result came. center_frequency (float): the center frequency (in hertz) of the sources. effective_dt (float): the effective time step (in seconds) along the time axis of the wavefield. This can differ from the simulation dt if @@ -1216,7 +1216,7 @@ def load_result_from_disk(filepath: str | pathlib.Path) -> Result: in 3D. Args: - filepath: The path to an existing result file previously saved via + filepath: the path to an existing result file previously saved via Result.save_to_disk. Returns: diff --git a/src/neurotechdevkit/scenarios/_time.py b/src/neurotechdevkit/scenarios/_time.py index cd5cd7d2..8a77b8aa 100644 --- a/src/neurotechdevkit/scenarios/_time.py +++ b/src/neurotechdevkit/scenarios/_time.py @@ -35,7 +35,7 @@ def select_simulation_time_for_steady_state( None, this time will automatically be set to the amount of time it would take to propagate from one corner to the opposite and back in the medium with the slowest speed of sound in the scenario. - n_cycles_steady_state: The number of complete cycles to use when calculating + n_cycles_steady_state: the number of complete cycles to use when calculating the steady-state wave amplitudes. delay: the maximum delay (in seconds) in the sources to account for. diff --git a/src/neurotechdevkit/scenarios/_utils.py b/src/neurotechdevkit/scenarios/_utils.py index 6b357a9a..09b47338 100644 --- a/src/neurotechdevkit/scenarios/_utils.py +++ b/src/neurotechdevkit/scenarios/_utils.py @@ -21,12 +21,12 @@ def make_grid( at simulation time because it depends on simulation parameters. Args: - extent: A 2-tuple or 3-tuple containing the dimensions (in meters) of the + extent: a 2-tuple or 3-tuple containing the dimensions (in meters) of the simulation. - dx: A float describing the distance (in meters) between grid points. - extra: The number of gridpoints to add as boundary layers on each side of the + dx: a float describing the distance (in meters) between grid points. + extra: the number of gridpoints to add as boundary layers on each side of the grid. extras are added both before and after the grid on each axis. - absorbing: The number of gridpoints within the boundary layers that are + absorbing: the number of gridpoints within the boundary layers that are absorbing. Returns: @@ -237,11 +237,11 @@ def create_grid_elliptical_mask( Array elements are True for the gridpoints within the ellipse and False otherwise. Args: - grid: The simulation grid. - origin: The coordinates (in meters) of the grid element (0, 0). - center: The coordinates (in meters) of the center of the ellipse. - a: The radius (in meters) of the ellipse along the X-axis. - b: The radius (in meters) of the ellipse along the Y-axis. + grid: the simulation grid. + origin: the coordinates (in meters) of the grid element (0, 0). + center: the coordinates (in meters) of the center of the ellipse. + a: the radius (in meters) of the ellipse along the X-axis. + b: the radius (in meters) of the ellipse along the Y-axis. Returns: The 2D boolean mask where gridpoints within the ellipse are True. @@ -261,10 +261,10 @@ def create_grid_circular_mask( Array elements are True for the gridpoints within the circle and False otherwise. Args: - grid: The simulation grid. - origin: The coordinates (in meters) of the grid element (0, 0). - center: The coordinates (in meters) of the center of the circle. - a: The radius (in meters) of the circle. + grid: the simulation grid. + origin: the coordinates (in meters) of the grid element (0, 0). + center: the coordinates (in meters) of the center of the circle. + a: the radius (in meters) of the circle. Returns: The 2D boolean mask where gridpoints within the circle are True. @@ -284,10 +284,10 @@ def create_grid_spherical_mask( Array elements are True for the gridpoints within the sphere and False otherwise. Args: - grid: The simulation grid. - origin: The coordinates (in meters) of the grid element (0, 0, 0). - center: The coordinates (in meters) of the center of the circle. - a: The radius (in meters) of the circle. + grid: the simulation grid. + origin: the coordinates (in meters) of the grid element (0, 0, 0). + center: the coordinates (in meters) of the center of the circle. + a: the radius (in meters) of the circle. Returns: The 3D boolean mask where gridpoints within the circle are True. @@ -310,10 +310,10 @@ def _create_nd_ellipse_mask( Array elements are True for the gridpoints within the ellipse and False otherwise. Args: - grid: The simulation grid. - origin: The coordinates (in meters) of the grid element (0, 0, ...). - center: The coordinates (in meters) of the center of the ellipse. - radii: The radius (in meters) along each axis of the ellipse. + grid: the simulation grid. + origin: the coordinates (in meters) of the grid element (0, 0, ...). + center: the coordinates (in meters) of the center of the ellipse. + radii: the radius (in meters) along each axis of the ellipse. Returns: The 2D or 3D boolean mask where gridpoints within the ellipse are True. diff --git a/src/neurotechdevkit/sources.py b/src/neurotechdevkit/sources.py index d8a43764..38b132dd 100644 --- a/src/neurotechdevkit/sources.py +++ b/src/neurotechdevkit/sources.py @@ -735,7 +735,7 @@ def _validate_num_points(self, num_points: int) -> int: num_points, the number of points that the user requested. Returns: - the number of points that can distributed evenly in `num_elements`. + The number of points that can distributed evenly in `num_elements`. """ quotient, remainder = divmod(num_points, self.num_elements) if remainder > 0: @@ -825,7 +825,7 @@ def _broadcast_delays( delays: the delays (in seconds) for each element of the array. Returns: - the delays (in seconds) per each source point of the array. + The delays (in seconds) per each source point of the array. """ point_source_delays = np.zeros(self.num_points) for i, slc in enumerate(self.point_mapping): @@ -853,7 +853,7 @@ def txdelay( the source is placed. Returns: - the delay (in seconds) between two consecutive elements. + The delay (in seconds) between two consecutive elements. """ tilt_radians = np.radians(tilt_angle) phase_time = pitch * np.cos(np.pi / 2 - tilt_radians) @@ -936,7 +936,7 @@ def _set_element_delays( class PhasedArraySource2D(PhasedArraySource): """A phased array source in 2D. - This source is shaped like a multiple segments in a line. Each segment can emit + This source is shaped like multiple segments in a line. Each segment can emit waves independently. It has no focus currently. A focused implementation will be supported in the future. This source is composed of `num_points` point sources. Distributed evenly in `num_elements`. @@ -1037,7 +1037,7 @@ def calculate_waveform_scale(self, dx: float) -> float: class PhasedArraySource3D(PhasedArraySource): """A linear phased array source in 3D. - This source is shaped like a multiple rectangular segments in a line. Each segment + This source is shaped like multiple rectangular segments in a line. Each segment can emit waves independently. It has no focus currently. A focused implementation will be supported in the future. This source is composed of `num_points` point sources distributed evenly in `num_elements`. From 949535706c8ebe9d14d07de9e1519dc51b3ed2ad Mon Sep 17 00:00:00 2001 From: Newton Sander Date: Wed, 17 May 2023 12:40:58 -0300 Subject: [PATCH 072/198] Extracting results to its own module --- docs/api/results.md | 5 ++-- src/neurotechdevkit/__init__.py | 5 ++-- src/neurotechdevkit/rendering/napari.py | 4 +-- src/neurotechdevkit/results/__init__.py | 27 +++++++++++++++++++ .../{scenarios => results}/_results.py | 6 ++--- src/neurotechdevkit/scenarios/__init__.py | 25 +---------------- src/neurotechdevkit/scenarios/_base.py | 15 ++++++----- src/neurotechdevkit/scenarios/_metrics.py | 2 +- tests/neurotechdevkit/scenarios/test_base.py | 3 ++- .../neurotechdevkit/scenarios/test_metrics.py | 2 +- .../neurotechdevkit/scenarios/test_results.py | 4 +-- 11 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 src/neurotechdevkit/results/__init__.py rename src/neurotechdevkit/{scenarios => results}/_results.py (99%) diff --git a/docs/api/results.md b/docs/api/results.md index b4c43afe..388b9f56 100644 --- a/docs/api/results.md +++ b/docs/api/results.md @@ -1,6 +1,5 @@ # Results -::: neurotechdevkit.scenarios +::: neurotechdevkit.results options: - show_root_toc_entry: false - filters: ["!Scenario", "!^_[^_]"] \ No newline at end of file + show_root_toc_entry: false \ No newline at end of file diff --git a/src/neurotechdevkit/__init__.py b/src/neurotechdevkit/__init__.py index 3e9634e3..9e0c595b 100644 --- a/src/neurotechdevkit/__init__.py +++ b/src/neurotechdevkit/__init__.py @@ -3,10 +3,11 @@ import os -from . import scenarios -from .scenarios import load_result_from_disk +from . import results, scenarios +from .results import load_result_from_disk __all__ = [ + "results", "scenarios", "make", "ScenarioNotFoundError", diff --git a/src/neurotechdevkit/rendering/napari.py b/src/neurotechdevkit/rendering/napari.py index ea2f355c..2d049506 100644 --- a/src/neurotechdevkit/rendering/napari.py +++ b/src/neurotechdevkit/rendering/napari.py @@ -10,7 +10,7 @@ # scenario during module import. This affects type-hinting in particular, # so forward references (in quotes) need to be used. # This should be fixed in the future. -from .. import scenarios, sources +from .. import results, scenarios, sources class _NapariViewer(Protocol): @@ -89,7 +89,7 @@ def render_layout_3d_with_napari(scenario: "scenarios.Scenario3D") -> None: _create_napari_3d(scenario=scenario, amplitudes=None) -def render_amplitudes_3d_with_napari(result: "scenarios.SteadyStateResult3D") -> None: +def render_amplitudes_3d_with_napari(result: "results.SteadyStateResult3D") -> None: """Render the scenario layout in 3D using napari. Args: diff --git a/src/neurotechdevkit/results/__init__.py b/src/neurotechdevkit/results/__init__.py new file mode 100644 index 00000000..4d1a5652 --- /dev/null +++ b/src/neurotechdevkit/results/__init__.py @@ -0,0 +1,27 @@ +# noqa: D104 +# preventing package docstring to be rendered in documentation +from ._results import ( + PulsedResult, + PulsedResult2D, + PulsedResult3D, + Result, + SteadyStateResult, + SteadyStateResult2D, + SteadyStateResult3D, + create_pulsed_result, + create_steady_state_result, + load_result_from_disk, +) + +__all__ = [ + "create_steady_state_result", + "create_pulsed_result", + "load_result_from_disk", + "Result", + "SteadyStateResult", + "SteadyStateResult2D", + "SteadyStateResult3D", + "PulsedResult", + "PulsedResult2D", + "PulsedResult3D", +] diff --git a/src/neurotechdevkit/scenarios/_results.py b/src/neurotechdevkit/results/_results.py similarity index 99% rename from src/neurotechdevkit/scenarios/_results.py rename to src/neurotechdevkit/results/_results.py index db9632c6..a120bd4f 100644 --- a/src/neurotechdevkit/scenarios/_results.py +++ b/src/neurotechdevkit/results/_results.py @@ -15,8 +15,8 @@ from matplotlib.animation import FuncAnimation from .. import rendering, scenarios -from . import _metrics as metrics -from ._utils import drop_element, slice_field +from ..scenarios import _metrics as metrics +from ..scenarios._utils import drop_element, slice_field @dataclass @@ -1235,7 +1235,7 @@ def load_result_from_disk(filepath: str | pathlib.Path) -> Result: scenario.add_source(source) scenario.current_target_id = save_data["target_id"] - result_type = getattr(ndk.scenarios, save_data["result_type"]) + result_type = getattr(ndk.results, save_data["result_type"]) fields_kwargs = dict( scenario=scenario, diff --git a/src/neurotechdevkit/scenarios/__init__.py b/src/neurotechdevkit/scenarios/__init__.py index b0eff17b..c02c5238 100644 --- a/src/neurotechdevkit/scenarios/__init__.py +++ b/src/neurotechdevkit/scenarios/__init__.py @@ -1,35 +1,12 @@ -# noqa: D104 -# preventing package docstring to be rendered in documentation +"""Scenarios module.""" from . import materials from ._base import Scenario, Scenario2D, Scenario3D -from ._results import ( - PulsedResult, - PulsedResult2D, - PulsedResult3D, - Result, - SteadyStateResult, - SteadyStateResult2D, - SteadyStateResult3D, - create_pulsed_result, - create_steady_state_result, - load_result_from_disk, -) from ._scenario_0 import Scenario0 from ._scenario_1 import Scenario1_2D, Scenario1_3D from ._scenario_2 import Scenario2_2D, Scenario2_3D __all__ = [ - "create_steady_state_result", - "create_pulsed_result", - "load_result_from_disk", "materials", - "Result", - "SteadyStateResult", - "SteadyStateResult2D", - "SteadyStateResult3D", - "PulsedResult", - "PulsedResult2D", - "PulsedResult3D", "Scenario", "Scenario0", "Scenario1_2D", diff --git a/src/neurotechdevkit/scenarios/_base.py b/src/neurotechdevkit/scenarios/_base.py index 69c6e189..91af42e9 100644 --- a/src/neurotechdevkit/scenarios/_base.py +++ b/src/neurotechdevkit/scenarios/_base.py @@ -14,7 +14,7 @@ from mosaic.types import Struct from stride.problem import StructuredData -from .. import rendering, scenarios +from .. import rendering, results from ..sources import Source from ._resources import budget_time_and_memory_resources from ._shots import create_shot @@ -363,7 +363,7 @@ def simulate_steady_state( time_to_steady_state: float | None = None, recording_time_undersampling: int = 4, n_jobs: int | None = None, - ) -> scenarios.SteadyStateResult: + ) -> results.SteadyStateResult: """Execute a steady-state simulation. In this simulation, the sources will emit pressure waves with a continuous @@ -449,7 +449,8 @@ def simulate_steady_state( # put the time axis last and remove the empty last frame wavefield = np.moveaxis(pde.wavefield.data[:-1], 0, -1) - return scenarios.create_steady_state_result( + + return results.create_steady_state_result( scenario=self, center_frequency=center_frequency, effective_dt=self.dt * recording_time_undersampling, @@ -466,7 +467,7 @@ def simulate_pulse( simulation_time: float | None = None, recording_time_undersampling: int = 4, n_jobs: int | None = None, - ) -> scenarios.PulsedResult: + ) -> results.PulsedResult: """Execute a pulsed simulation in 2D. In this simulation, the sources will emit a pulse containing a few cycles of @@ -522,7 +523,7 @@ def _simulate_pulse( n_jobs: int | None = None, slice_axis: int | None = None, slice_position: float | None = None, - ) -> scenarios.PulsedResult: + ) -> results.PulsedResult: """Execute a pulsed simulation. In this simulation, the sources will emit a pulse containing a few cycles of @@ -608,7 +609,7 @@ def _simulate_pulse( # put the time axis last and remove the empty last frame wavefield = np.moveaxis(pde.wavefield.data[:-1], 0, -1) - return scenarios.create_pulsed_result( + return results.create_pulsed_result( scenario=self, center_frequency=center_frequency, effective_dt=self.dt * recording_time_undersampling, @@ -1011,7 +1012,7 @@ def simulate_pulse( n_jobs: int | None = None, slice_axis: int | None = None, slice_position: float | None = None, - ) -> scenarios.PulsedResult: + ) -> results.PulsedResult: """Execute a pulsed simulation in 3D. In this simulation, the sources will emit a pulse containing a few cycles of diff --git a/src/neurotechdevkit/scenarios/_metrics.py b/src/neurotechdevkit/scenarios/_metrics.py index 63ec8813..f7cb6762 100644 --- a/src/neurotechdevkit/scenarios/_metrics.py +++ b/src/neurotechdevkit/scenarios/_metrics.py @@ -3,7 +3,7 @@ import numpy as np import numpy.typing as npt -from . import _results as results +from ..results import _results as results def calculate_all_metrics( diff --git a/tests/neurotechdevkit/scenarios/test_base.py b/tests/neurotechdevkit/scenarios/test_base.py index 6d5b3237..785542bb 100644 --- a/tests/neurotechdevkit/scenarios/test_base.py +++ b/tests/neurotechdevkit/scenarios/test_base.py @@ -7,7 +7,8 @@ from frozenlist import FrozenList from mosaic.types import Struct -from neurotechdevkit.scenarios import PulsedResult, SteadyStateResult, materials +from neurotechdevkit.results import PulsedResult, SteadyStateResult +from neurotechdevkit.scenarios import materials from neurotechdevkit.scenarios._base import Scenario, Target from neurotechdevkit.scenarios._utils import make_grid, wavelet_helper from neurotechdevkit.sources import FocusedSource3D, PlanarSource3D, Source diff --git a/tests/neurotechdevkit/scenarios/test_metrics.py b/tests/neurotechdevkit/scenarios/test_metrics.py index 0b6a5815..cdb09d49 100644 --- a/tests/neurotechdevkit/scenarios/test_metrics.py +++ b/tests/neurotechdevkit/scenarios/test_metrics.py @@ -3,6 +3,7 @@ import numpy as np import pytest +from neurotechdevkit.results._results import SteadyStateResult2D from neurotechdevkit.scenarios._metrics import ( Conversions, calculate_all_metrics, @@ -13,7 +14,6 @@ calculate_i_ta_target, calculate_mechanical_index, ) -from neurotechdevkit.scenarios._results import SteadyStateResult2D CENTER_FREQUENCY = 1.5e6 AMBIENT_PRESSURE = 2.5e6 diff --git a/tests/neurotechdevkit/scenarios/test_results.py b/tests/neurotechdevkit/scenarios/test_results.py index 298e5bc4..c011bb3d 100644 --- a/tests/neurotechdevkit/scenarios/test_results.py +++ b/tests/neurotechdevkit/scenarios/test_results.py @@ -3,8 +3,7 @@ import neurotechdevkit as ndk from neurotechdevkit import scenarios, sources -from neurotechdevkit.scenarios import _metrics as metrics -from neurotechdevkit.scenarios._results import ( +from neurotechdevkit.results import ( PulsedResult2D, PulsedResult3D, SteadyStateResult2D, @@ -12,6 +11,7 @@ create_pulsed_result, create_steady_state_result, ) +from neurotechdevkit.scenarios import _metrics as metrics @pytest.fixture From 508e8d462e271c99d4b34a8fe7b29f938999c171 Mon Sep 17 00:00:00 2001 From: Newton Alex Sander Date: Wed, 17 May 2023 16:24:46 -0300 Subject: [PATCH 073/198] Adding process section to 'Contributing' (#31) * Adding process section to 'Contributing' * updating PR template to mention the labels * add comment about redirects not available --------- Co-authored-by: Diogo de Lucena <90583560+d-lucena@users.noreply.github.com> --- .github/pull_request_template.md | 8 +++++ .vscode/settings.json | 2 ++ docs/contributing.md | 44 ++++++++++++++++++++++++++++ docs/images/circle-ci-artifacts.png | Bin 0 -> 225046 bytes docs/images/circle-ci-check.png | Bin 0 -> 228123 bytes 5 files changed, 54 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 docs/images/circle-ci-artifacts.png create mode 100644 docs/images/circle-ci-check.png diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 25eec27c..38c3a33e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,11 @@ +#### Labels + +Assign of these labels to the PR: +- norelease +- release:patch +- release:minor +- release:major + #### Introduction In the introduction, please describe the type of change introduced by the pull request and its purpose and link the GitHub issue that this pull request addresses if possible, or add any reference that would provide more context to the reviewer. diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index a269ddb2..9bb55364 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -128,3 +128,47 @@ Before opening a pull request, please make sure that all of the following requir 2. all public API classes, functions, methods, and properties have docstrings and follow the [Google Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings) 3. docstrings on private objects are not required, but are encouraged where they would significantly aid understanding 1. testing is done using the pytest library, and test coverage should not unnecessarily decrease. + + +## Process + +### Versioning + +NDK uses [semantic versioning](https://en.wikipedia.org/wiki/Software_versioning#Semantic_versioning) to identify its releases. + +We use the [release on push](https://github.com/rymndhng/release-on-push-action/tree/master/) github action to generate the new version for each release. This github action generates the version based on a pull request label assigned before merge. The supported labels are: + +- `release-patch` +- `release-minor` +- `release-major` +- `norelease` + +### Automatic release + +Merged pull requests with one of the labels `release-patch`, `release-minor` or `release-major` will trigger a release job on CI. + +The release job will: + +1. generate a new package version using semantic versioning provided by [release on push](https://github.com/rymndhng/release-on-push-action/tree/master/) +1. update the `pyproject.toml` version using `poetry` +1. commit the updated `pyproject.toml` file using the [git-auto-commit action](https://github.com/stefanzweifel/git-auto-commit-action/tree/v4/) +1. push the package to pypi using [poetry publish](JRubics/poetry-publish@v1.16) +1. build a new docker image and tag it with the previously generated semantic version + +Pull requests merged with the tag `norelease` will not trigger any of the actions listed above. + +### Checking NDK documentation on CI + +All pull requests trigger a CI job that builds the documentation and makes the built files available. + +To check the generated documentation in a pull request: + +1. Scroll to the bottom of the page and click on the `Show all checks` link. +1. Click on the details link of the `ci/circleci: build_docs` job. +
+ ![Circle-ci-check](images/circle-ci-check.png) +
+1. In the opened Circle CI page click on the `Artifacts` tab and then choose the page you'd like to see (e.g. `index.html` to load the documentation root page). Note that each page has to be opened individually from the `Artifacts` folder, as redirects between pages do not work in this setup. +
+ ![Circle-ci-artifacts](images/circle-ci-artifacts.png) +
\ No newline at end of file diff --git a/docs/images/circle-ci-artifacts.png b/docs/images/circle-ci-artifacts.png new file mode 100644 index 0000000000000000000000000000000000000000..9854e93138e986a8ba5ee24d809ec0cf42cc4641 GIT binary patch literal 225046 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 -n_i0Oyg3{Gwbt-Q?!Lfc6oMqcaF!pfZbZOXV&{ z({Ho^FNbuHervwD(Kx|zrHEwr)k$kE6~86RC4yVm5Z-Z)#bSH&RVi1fH>{`=5 zxISMQvV4~NUo;MAB7SDQ88e$W`htRnswdyDQpEEFJraA}@$_;*A8b0rJK-|{GF=~d zu#}X3dpxGn4_F+1a@4uII|9rg+Kpv^XXeYMx{k805YN`z0=@DT4mRMEGho*ITJ0y< z`?os|#d#`Pr;4F8oTr*pkA;d!%;0mnGusdF1Z{fb?zQ{u7UCXw@^`4(<=KDDcA1PX zWmIlGG%l}l4f{zl4xX?Vb>kE+!425g0ezD_`$$rrp-Y=<}7Y>-r2mBfV<)9 znU)S)u>7@{V_37K)+fGeQ-KCu=c~(&rWNFm0um z<9kd8sOj89Te8;i-nQfAZdaEK!(yqF>4Y1<)43dNt>ngNe#F2sA%om66^R>S=XTSb zic>Yym&13uypHRt2)N5`jH(|ne~1-n`}iiko!e7>-Tj73+t42iNGWXD6c1gv)EX<*Vu+xXC9 zNm_6^HGGrg!Q@l*1lrWDJ?=2$G8@f%eSv3b;c-b8lc?|I)^bn>%r1IuNk8hNwZT}D z=kHgp7Q}A*lJ6pcpXE8deuof6CkTFv(&){3v^Vk!N<}X4_i*{oz)AZ>QVok(>p?>i z#sHM?KE(X%<^A`=mkb0nDG+m#|Lt?Y10F@kl$4(#GHv=>i2m2#1jg@Q4<&pVY@Oec<`+&w=CGCLz|Be0sdZxclER*9y^$#zAzyA{56X0v~nklmX{f_?2{n9Ia zdEl5QW(N!VFBkdW5B*;O9}PAzC;eXsBbXNjbvWj8{mI?ePrv879slg%DC}2Gx`N(3# zE1H5XA4WJ@W_-#{&{5mT6Rn4^U=_vUE;t0rs!e z1Jz%}H03^gLNzh!|K-M<`P_<9myssbk)_hh*7)C^bDtE*^XB0}^K?LZScB?;w-dAR zMm#>b-?Z0f+^+_y8X{SQ&K9Ti^|`hl-DJAk_qQSCPfBKoU;MkgOp=?J_JJ-Ah*DSX zfzL0JU%&dI53rejtpsT35`6~y$gTj*&bSX*LcXTL8(&-7%tiuQj?ZvQ@|mtFw- z=0BMPH}`mm4r(VdJdUEjwGGwh2Wef22^qz3y|?CB%+UQX{2!_IKMwuh1G%di)STvi z=%d?e&}L13k->X2XK&e5MRdX?4Y7E?O~1|9=6Ut`yt5)8$&>$i*l{1S$;EIQ ze1Vg@J}$kmi4f(!d0p`5RK~DT6LUT8k90W|eS`(?3(}kX*#Gfr?(edlC>T#qqk84d zB4C#lQ&hw<3-1acg6e)`kE9W_m2A1av`FSP|9TC$`Py$!arb!PlXJCANI|`>59E8c zP=$V2&k6TB3H50S{TFkG0kuK!?#t%jwEzjAyxO=n4&AlpDV% z!yl>RCmd(*^2VD7SvP}1RL$l+C`WXQw zfJb0qVZngbH2gWBxk7(sgPuqld%!dHjlF&4D$|<;_87p1SbA+J-J;m^5eoVt=`dd= zpI@A>q#fDtndSnr)*6~yhaYgu@b8}aIjgEA>Tnfg@GC%dk-WXVvgyfodwR7L1Ojvl zf(V>?fWDG%HCq!oX-8}8b8&=3J{C{?KA0+?f=46FcyMq4Az1+Sr$KG|LJX^LEALgi z_qKSorDN*KF3{??<*IY%^L7S72RBy{6B~nuK#QnyM_7adJ@wA|s2U%=C9330m!LR#VRaNzjfuZw2@DYJz8ylC) zZxfk+aZX9#}(Z!U_K{$Ypk1F_HJEDT#hfqh`G79$ok!m%VBwdI!&g^sis( zn?25))2th%{4neRE5OjQi&ELD)Sv6bf84)gq&hv`b=sPsILWx?a_f= zr`zfU9RWQS1-zcW#>cN(=YRfP!sB)AdL~YVNxP_RaP5mlr*)>~_v_55>B9?)`DvF& zhk_+2+uU>V{oC;lE5GZ@DW*rnm}v23pZ8b-YH z&P?z2bd?EmDBT`dg5+D)ABt4z+DPkhe11KdaxjZh)Dz@e_bZEetDDhkZPrqmakS}= zQAzdIiuR^pUWGuIlRXmoWccj50bCx>cJR)gCKnZmbpIp1k@X4l{yJYA)yqXt9*%3M z)PvJ+@&(Gr+J2r1kEU>l8MHlMz}&I%NJFv@SDR~$;6DUbA14>IxA5!Er3zOVL_De$ zA(pu%`p8`(9d;EfK9;wYD&WtKsaxhKRIFO&yDeBJ9cco>uKuP&20^{d*t$|CbxaZ? zZ}@kQ8eNT6IM|A$jurO_0EkzuJI-npDE7MSPgmg%22b!;bHHh8dlfHnH5#SoQ-`tjX!Oj%mVo7_gP-~1mnK^ot9CQ#7LGF+KqnWHEna2-yEo;s z(WX&cz1E)Q=+u+U*vFuCuRF=N?BB(XXX>=2JXuceR=A`V2AYL~6JHJhe2^y2yprh2 zlTVxD#rMsyv?c;?0oLw)2zmI}_R-OxX0=%iyH;fvm*&sU@!Y2=d=?5oOv`_h_~L6^ zTpV&|+~#EScMZ~M_m39ahcZhK#pjcyTE>4CcKreP?+t)FvIjZN{Xdkw2T+q;yESY< zuz><10wSoWAWgb-P(kTP@1XQvL$9JDNRciz2+|=)?;t9@gdSP|rG^kfO9G*NoAaG> z-f#YSeINdr%;*eoXWzT7eO+r^%Tl0%eWXA$7BG&+)>MPgk&L4Ex0c|Epggmppxu~> z;z6;WD5%#R5rr4v;(~oATrfW?>c093)E9i_f4FNo-sD%m{23Sxx@8^F8aM?l6DL|Y z#>dBb9jC=~znKJq&0I#%ahfCK3R6N`1Ovl3I=N-KNS7}k1Wtn-QxXy^ihdpfmPkjA zQp)j$?Xtc6(t0>AIu)2rwTVNDf~FNF5mk^h55GgAu;+TlaIwBND4fFb6DAjXKNBlm zlUQ5W6ni$GEOIA2;va2 zwrnJxcX{Z6d1p>`ZC_UA&iUm%7}l@-K4X|lE{g3h`$J&h%$Zj-fujPcxGZ%GG<_e~ zvZXIb_}ExU)%y_E+KBxY2S78bK7V@oV2_{>bxV96bn2se^WAiCu*R`#D}zd_)?UEb z?cV*i-2tU^FTjBR+Zg3e%?kwh?#p ze%WOPLB*KZSXaX}FK)&;;Q3~vSW*%&dcZ~u4so7q>nw*$uuoRmcSAb=w0`7-07i9! z7>3B_3Vi!?SAJSAwO4KhPo!zVq>5of20AiWfu*Ptv+;z5(=(ebAF*&6-{~65tRmz_ zb*YxqmR>Il*L~$bb6wpX4DY+Y3IAN^mRuz9mE_{^KJACSAVquNnnCvOx9mVER+7x8 z16GeFhH)tZw&VHxAga#`j+Fs{EP;-%OynH4X}4S-PUOHIV08IScR)&az3AbvJ$b5) zV4nK=_-Z&QfuA{Fw8YZ;9RkpK$rcc{@%YQCH=vjHr0cWy! ziS97HLG>jGpXw~rn>?@IMw5$!+%NWLEnd4w=arcXVP&LSa3>XgM!OYI(C&lSb8RXR z`N}5LB86oPtDS;zWT(zy;C_sIG!>z~#sTf`s+acYx8AgkFm#UO8k?_mAp-pFA{yM& zrZ$zYcsVR?ZGI=aB3E5q*Ij@q40q+%C$=cu`t-Fdbj@~*)&ES3KlUT^l(_6)5Slzp zYK%0)FWUB#Zf@4$Z19!v{DNLrf$upGZ$-HCLy_KUQXcV-%DaXdGUZAFs>_Z^q8>0{ z5leoUZuGiu$a1p8Sh6`dlb!^?%8a?4EouXZ9Sakgn!5S|C|*mGriYx##4czX`z?6Z zmGD^Q)F{gxwlf7hHp03|VTgDPJAi|{IiwyU@S>vw!i-EVIUMd|XH=Nf19psfuu>+9 zx^Ttno@giP{d?c?i47?o3Y*~|% zx62=s;^Ko{*q)Fo`540r{tYBU)v3<-_#LtRWlEkpb5G^F*R7=|Hws~lVy=sdgo^L< zH%3v_xH%2}?s2H1GT&oy^R8=?IYa`MTk>!xYwxo|?$@t7J$3HTo1_fMoWv0fpp6fF^L7-K18xp`-*>3;m6{YDmQ2>IwLB~CUj)# zd8~TuJ#ltqBeu2_^WA-5)+2i}K72)fL)dn_c?{>3AEVluceIR%T1aVb!Zi$!-5T<% zSoR*&YdheV_SZf7Sc=)bV!wyKKreqly`rF)e`CDPv9M?gpW7fBkh&^9zrTd~ zbDuH$X)NBwWh6FRRt+NTG#_X;QLig;7-8hvHlo(tB}YZe@L=-F z{sRVg`%h5;d(RT~&JR}Q`6W;J8hHq3udu;3v=g>_+~y`^U`0f>k2zh^iMfVs+I6W@ zo_O-XoDCVZ@*kmX;)JiO5S&M>Z;fX%$KjH8efJxqTx`I{3C=guT;zYvja)01iYCbr z=r2^a^dXCZJhVHoTGv4o;?m#v%y&fB!ZuoSF=zpLJ3N<2t4A;?xf!w!vi1~YgK75N zp*Ma+)YLq_YpWRnoRhhsL;P15MfhKz>Blw_(pSeyf9Cpv(>x+S3qr)aA${sM%^Db7 zr-fht{nO8gj)8%_Sg(?byvAm_#C)L}cn6P}`jB*s|OqCoCwu#0%xztHwOLt?e*&prVQ$D{$}5sLGA- zB85UEZ*v(nyTo5&Ai2J8bcl7S-AF}8;Q3Ga_Q_dx|B1k#?!Se_^+1e^9|bqn8Rj?O z$S&*UX1}T=RhzDSUy*RpJT}}B9*`Q)wadG{`8@kc?ZxyFwGw&pxuM^~`1h?>Y+e0p zzCKu~8@}SP#?7dLF{xFaw9aSJ4#b6BPwdWOl5}7&VDj8Gb+Im2;6RKP=%{bIzWO=J z`>KV7uG!>`S^eRh?kyLkr~Vi^gr`=h#e0P0Z9%4B#9f`g{s$VHQ%{de^3K zroaFt4wsaav%VWmRl0;qLydMxsiW;epk=lO}=>xTyRj*Wa*G409=6Mnr{NrCy@Q-ZnWtB2>nR{)=IVoL&k(GmQmbDop2`YUNU9iY z)I{^Fj+e9>NZZ0^%Pp(}>TO^}$kI{2i6mU#Cm+#m2sVYmqq?@6(RcLJR#?E%qq6Mk z)ydbXnL~jy&abXm2iS;iQO)hGXld65!@biitWNGo)^ z61lm7f-KVh(X?2*w#leiZcTs+0E~wRLJuDQ5l+{uwCM!Yo>o3l?Obh#fy{S`PzmG? zUaXn5N+5O@IFYzDRFbMfy%y;dRJCS8Z;5-~fX{3^2{{{kU&-b1oy?>GZ!sR80hc~t zFlD*_Sy~(uM%oIBzfT&3Y#+FGS@~}dJU1Wv_9}t7P9g<>k@h_J$k>tgaum1NNA**B zh*X#srzOz>ZtGU;l$+VI?!*Rymt@84#VZvT_4Fq`8LoTvC}sJx&gRmyV7vwxdAAPX3*)xw*L#a7aP+MNkJShCt0QG?G>_LL@Ym_q2eNpbh|IOt ztG%00TX{Q9k#_{7J(jl$$CQkms(Q&_YJ9eZyYJThb6SAV$zp9=E5bj|zJglOJsVSAlKg{Ae_w=YYunMn|)s>jq$ ztNSvExQPw*;^(hwQPA-|qZe{m^&}&oN5yrzFI3O?mltxqgrwU-e6AE}ml*xI#y_%D zsQXLCu*z=yPKuy?8Rk8H072{m_E9NrsKHjeUVYc_utpM(>3ihu0VW|aAY=Gbap(+y zm0fP35JUMTlF4(~xc$$cZ+L+k`8^7*ezwe0s9I}$o#g<}I8B~ZKJqU ztnTgO@5Z?7#6~u>idg1cIXp+@J1WsGst8X*c)InzBHu|KZ}avyP)x0GoKiM&GQS_- zJY$(D{io{|T4HG=r@1{Pee9WXNeXoX)9y%j8Ft;Duuvy{K!8u}(f<M8mzETCsYYzYP2|z+w~ko%hJ8iq#-l=hxP>ezRHKAF+<}1 zCaUF6lnAxm)kRK@=(f5vbVT?fxDoP|<%^eh+t%ga1iPDl?uFxud;Jd5x3~7%1iXta zazaEdi5tuWs8Cn^l@qbat-5S0wHJF}NAXkC>891G2~_$tMmWt9|3G6#1oEv!t;9$i z)&C{fp0q&Ox=8&l8Lwt}iO>6?QV`Ocbbo#}ANcWo#PF04CKjtRk2VPE%qSlq@JGNr zMVA+fSxbWQSV@hcLauq2Jk)f_nX9bVhNp{1uoZ)Q7nv;POnZ2S*zZq&BRZlcJI`kOvv^U}*?(S95(ZxYXqNWS%01vNSM6EfBTkfPr zuN7^Gv-yOZ3d9t2vuUb0LxoJoY1iys?*fDvAJN#RL#>Z?Q(B%8><25FN<#K+X7O2c)pJ-n|mqGt)2=Yn6#<}a3Lw`olaB&J>wtY?HO#actOeA9z zS@fF`0`M+sFU?or>P)Iapjdi4MsaZvPJ8Sox}>>lAnSRyM!s6xZNpqWL`)TX1ie66 zVbw5dc*T9Z7X(_WQaymqs0(<-S?BY4xXA~k*%-Eu!VE>NXPO#$Fy5~YYQC>NuL*$b z?#5qE!@Vh$-MVB5^)P>!SdDR!egmJ#up}+VkZ)^aCAK#A-XkcNzK2-Jt{rNrZFnO2OOc;v_9W|ogw0iAbwEM~ zH-mpP@`z~_Z>NDU&=N8P9&DAC26qvu&Xv&u#fXDCl#uQsWe2_?!TxU>93YL-e1js! zX9~|gRtbK8c)j=2Hb;;Jt0;1)UOab8Y)@5VvZ+kY&wT8uJ$P12GtD@)P8Lk0I(*#qm&8`Q}g6F3fLQyL8x^Rmsx436=PZ1MH zBpiJ|%P?p)is;L_G%DG?SN3U@Z0q2P9GUgCzadLz=3`(1NOJN19>aSE#;3WBBhsDQ z=&C~0)zmm}^j9w-{(y4sF=zYfefzq)%KSL!8#pLhwNHtRnXc0W!e0JNhM>%rt_6;6 zLApH3cZgfdj^8Hjx)IeDe705Fz>#qk%}*!=SIq8XpbvCl{1&AB))OE~hnBdd%peEL zc=aky3!+!&m}N`iL-r+@(sz{MraV00RGo$!8;|6gvbUX?nR!RMLQc2o5Y!1_bG1$e zGc>h#ZhS1I4B_YHOE=SfyGF&$epNixHEnds@&FyDfes+}HZPLxI^e{hP|Zm#uj%RY z5;MGbL6ksgl%nA_2q{*#`iY4=)Z?;u4{h+@DvZ$>B}SSnR5dJh@ix0`Ysv;1=klp^ zE4mu%i{~d_u0NEi&1_v*t5dbVHoC`Da5(hWd}Lsw)EAW&WU;aXwc=%-RZeJ zgW56=O`SE`#T%)k0a~9MMgs&l8gjo2qH+2N2BD8gk%7fhz>iK&k=}*rlx8j`uLBBY zMK~Q_+s5+?(?Ua+y~&Nx8b}cEsM`h+qP@5~NXgq9B;!eC?5ut|Rpv>BX}7BE8=a7< zC?TA`X@R+T%jG9gsKbH=BIZU?qf6wT2>Fl5TI&iQstLCy&_)b(GQ~q?Ep^Z+W3*o4ILE-t_S_Fbge~GUxRdW)Go&4# zaT$x3f$73&x;6Q^HhNV13L_$XgmjgY*0)&3zu5+7_dyxS?;lnA#cz1c?Nxu(do8d# zlZ2?v-Ex@kDslj^72hg5S=b^)&i(GhfF2%+((Km@J>Hf#Y@Z-t8v6W!ejX|H^W8M% zh8`WIy(2G9vu_{d{@Yy5aSkp{Ea(Y5Q0oT(Ncz4?9>whdTK{n*&0T_s(3A{8BoD0& z6U)K4P59N=K34xO<n=6X$+xyU|ze!p8Oc-6YIyjDX ze?~tDIP_7rD8=jZMS`>(Y*N6%)*u&gSY;i$w{EH63Ts47F|_hA+ogY%#-IqkEQWfE zJ)~ur?}AxMy>@GZEAq8@1MiL@7oQZ2hAm%sD6>7`p}pBNbfw=Up_HTqH+7?Abt8@+ zl*#Z`ex&ei=EwveQT6ZBav=J9hL&}ktwy}QK;6zqr5xim<)F`(^*r(>8Q$Z{BF~QmPJOZZg_;9w^O4A{Lf!J5J)Hw0?w*K<9>^~(!uW)q&;<%&DXFRNVkWDF zB$CIA(8d8E8Q->g;p25qUX^m++?*VG-F^UTzYWulD90w}lHx`siw&7+1+6e+Ido?D z)JT05K{Nx%{BBG%P;gA9Hm_QiiyKDi1ea)17t$xS2G?JXaafvS4Y1!+z}MtVDL+hM z$wk<+JcLrXXw{=4wE0AqGsJ zFI3i+bbnxYPlvD-eV~bkN5ctJoysxIL&p_cvd;As`p*xEExwMj#TXk`T2=s_6FT70wZ z_vYNhF3hj_&|v=-na;YX?T}rZFV|`vCpx zhvhm{-7Foo51@DHcLYI1?PIS}Qnf`oyOp2V=i>B|+R+cA);&UN(^dvU`CbnQwG8e@ zbDJ}B(^pz$!Sqnwju!k+7oe}Fm-jsgJ2dn5PKcYUNg_bd!d6IoK%Jc(4>%nJY zd__3qP^tY?&A^@punamfP7y*xr-rozlXW+EZMi%=3_t_!O5&@Zkz{A1ZVKN4ps=**1@e%190Y-unzqxcJ6QS@k=vy=*y$?I4i>Q&V6VeaaoxWXqAL`aD*LvE zgbcfPiRJag%QeX@7JTRJ*JxRGigbUaqhBoL!s%5T9an;RwH+*8_I4hq z+!&HgAq+@4Rl7PkMd=6kABh$gdg-_|&MoJ$$~m30!gSryUU#2Tnah5To(vBxA&tY$ z>GGEGnsxMiDRUcA^Qa{K3QgI4bGdO%S2({*uC2U*xLr8a-WzcbW=rZv{90@4n`@a} zze3tQqXLzx2xB$rvH-Q7bfWYt<~(dX0OhGZff^ zL|e``!{hkHj-IQqxVRyJ_2wS=_J-XFoH7XcDqs=Z7Z2S0y4OC{uYs%`ZGA>@x(UJZ zmo>cu1i|F}n5MBkj!ivWs*{=Cc_0NXFKV=yWXLWiZSrw@iyM5Ra&*UC%jQQUeJ@yt7A_LU~BH zN8wT}?}zHzC-viN_aXXlx{VQ5lmuRD+`|(J(mBg_16S0U)&PHCCWCH-fae3>Rx43BJE7Bw>P)o z)htyNd@xNmNCwnmf0+&EBcyc9rECSu{rQ~)Y)3hc<5&%#5{z^jJxGyG|7mP1CM^sr zYXIJ`E)&+dav-4L3VB*CxrfJ~Z(M6wDVQ2}SC*Ex-;WTQ{$$i>V4IroFdl6**B<3z znZ78pT;Oz3aUzYOrw&XmL$@xo`n_i0$UO*RAhoMy-7ZL%jX$j}K$taOAjG~p!54LI zOF!2gOa!!jZUnrmi;8gu6eDaEbv4}c0EzRC&4oU72A?Qts4YmwKbv8zcL71}E|6eN zVz)JdAFP(c=K&e6w7w^=ynNV0F1N9YJxXlzgH@oogoFg*Roa;uwyVA?;G9+Saqtn$ z)J4!MB5)ZXBri8fV=USKO;mCi0(g^V3}ctKo4O6gJ6sKho_XLuE058&~UwgX_n^Lxu44s zM~va?el->%nm{PD?RmQa0y2}}fN|8Wj95d~GuV3@W!cK0E08jVZm9qO@STZHe%IiEHQEKGroxNV z>#f5oo1LAVtI!5}7o}|bVIaYIoctKCc7!g+b;mwX5lj_0gI%PNhL;B zPcPX3lwaBe!`JoW-6f<$l;mMeX;Y1*V6pw#cN)S@v)>JpRu+qK!f3u-pc{XNFdhx} z=v^JnAc8!zUHT%S+Jb!Y;JXw+Fx@SE<_T<6DQ`FpE>RGrWeU$ECt7t~pMX5A1kayxtocZ5a-3qaQgWfi_MK*&a?b zJp>650z_nSf!=d~jbo5Z_`ja+e}*2tO(P8-UoR>aTTE|ft@@M*^@ymIsoA!>}j1t ziA0G>f&;RS4*h|wT*=tpI>UhPO|b4Z8HtE`pa&hl)u+phBE$6}83*8YOy6RVTUjOW zR6pPsWjGV}6xg?Uy|=9tByk~kMH-t7!8Jy}q?qr#MzNlz``VN>$uRCYhw_olkjEgK zm4iprk365fkkZY=A8gW%@fJh=F7W46OxhB8nB)jXc}Wh#nVRVm4&yEQCQ8*H{XR5T zn_K1@&Ket4Ufxp?>zqG+hJz9`bp7PWr&U znH4^CSv&2|SpK!8((#CV%|flJA-l;m1{g#ITtdGtoF8l8 zjmnN}Popq!YB{GI64qb@EiB=ggs285ABRgtHJ1O2A7u4NKoa2piO;XaBoWKC#Ix_X zn|7W|fQ3GzfLua13NY+~0FxU+*dEl8W^dD*Qf}WWKr{GEo6IV)OvOM-dBo z+Z6g)ijdjz$b5_5WZ4W=KA{6Pk%e!h=OB&C4P9?9Q14SX*fQ?}RvUkr+?GvsUL+Lc zR5t-HQx4p2_E+8{>Z7{EQYqb*@;FmV`DlYd@cnz#u2u9+`d#8sVl_K z+K_qzy5}{WrJN?x)(DZe3y=ru;0QuZz|rAuQzA^fc*qk`tdH2xBHlfd_Wpo~F7Xg+ zM@hYYomtveseHRc8t24WUA=aDgsOnF(TAS(mI(SBYUv_9_e`1CEFPz4|8(-s&do=r z&y4f1)lEy+{yOo2oSwK+rL*ISXCwuBL{@B>3}0YQ<1sP zpFbNI4xicpUI};EwTi9=1iu+6)TTAO0C zk!XVYJUE!^hy4UGO=1r$O6rEgy$3p813uu^L+?lnKm_wHu9B8!2DI#EocMp3Xm&xY z2)+FA)>j|sUyKwj12*wOVLfnp=Q(2y!d1Nph>X`}^xfy-`M(6%HTV<+*4+G$uLO zh6FugC`t2Bqr)-r=b!!GuCiU>3&z*vjR9wzjl4kM(@u?N{>$GxQ5uJ$-=0!rGsQqe z5tL3`ak*FNglhjwQz6{of9g8jW;oSf&?l>J&}-^IL2Eany&*!Ek6&PYzLvDYqG_d~ zl{gSsP73{LUSMB-=*{5u zh<}S}jw6e2o2Axj(|5wbl%9T`Kp3!xiOq1VM&2a&BR;iz5gJY zxOo~#Cbo*`05cU7FK82>KMqHPT6|Kw%Ia{2IK8r0h~@cv3(82skn})u;d#UU)rI3J z0uodV!yve*uLZVWreQ&Aj!lv(#xbs^O`eegU+!WDXeTMr$#4;jU=q9)PdVuPmrWr< z>I_N7rC;r>03S;IK7Sd?r)I%%lMV#ynn^7ko;K<=|MvhAboD)HAa0TK z3=0Rv4(>cLSu4QK_j);Yg6^f{F(TFVC2{M*NML*v{)w6^_2O9K{x)6~Af2H8^T$bt zopaP?n?Ldahy2R9pv-F@VpwZ;8&c-+qvBuP6j7P07YTo3Kwdph4LbbQCi5~WfWDuY zg2Y4c9ozSyT|Eos^HM7<`vzUL=dbBxJQ-)HEnA>*K_(&O<98mObBH4S_jhP;osy3{ zb$U5wi-Qgnpm>%SsN8!1qCLk^R=IA)kE-Y1Guj04mYUBu#KPl2>hE;{M%8pk;%~q^ z{M>md+PRSDh+|ym7({af`vmeN-S3e{9is>O<{q04!k-g3P)R75dQ#cs!!J7f(0#=!r7pB7ks>a>+3<=;W-zdH2{70{nuWuX15?&@F9^}KX75P$s@ z6IgWrR+jLu7iMM#qkX#6nes2){r~8F0;{Edoy*|!C&~^sr~Sr!|Jw-tKYt>1`rPY_ zo_?iuVh4NUF}xMcFRBVBc}l4eYh|xT0z^(w2BBQ)mzk99sB4!iY<6oP>rQ(leUMe} z`<#Eh>tFvz@|+nk2w$3ag+24is68{#3724|Ywp>~GowC1zo-U%xFD6-v`!`G`TFY% zxBQ!hMI3Ve1yD1Nbf|j!^?&XzDXBA*)PY)zhFikF2WSnF`fvU0>xzqO@w?}W;y!)a z|2)V~n6EmY zwi4hNsxAB_xfA}5PgGnfhN)SfSDUNyKPZC!HH@=Rsb{1a@i{&5Lh`%*Dtd8cLu&VS zOGWS5;w~zPi~e5$pv=9&8@^l5B{^Y8lj6%Yn=Km&y6%ga8iQ>R+efzkVYF-zrvdb zSmTrRqcAwNTsfxn7qEU$x(pz(KE^_knCaVOObwcmm??|@Ai4TiSI&akFjgT#w+|0< zzxJ3PmERnhEvJ0zelF|ptnhzL1ud}OXho$q=COtbczysg8={@LmH7wT*|gfVXiH70nw|L64pej4-w3`H7Z z2*JGl{WldNCw8GHD@CY=_lsk!DH%b3W&E!}!2jJ&?`Q^|0+dXNx;U20^!Kv4m#u{N zmfM=|?f$+MljhQYk|}jH^vug~7**)mD^@+aNikx}w>ow9vlcc#%l5KZ&p!)&2sJs$ zczj?v&6}~cRbl%2-m4BYm5+sOx2CS`QCx7;cHHXS5YCg#&1e=N`Gu;8KCG%>ks!SP zRV9LOF&`~hh}j=Yi%CncIZ->V9UmoPsJt59n4SGeDxZm@ghD=TuK4+OY{Kd`|4AmO z6wCRmffVa(&*RzHL%AF7@+j%+>U9`2ja4eUF?II6s6NRuiE{%DIp_J~kA31r^pg&2Oj)L;BoudUfkH+<#%S0uErigcf3 zd{xx|R3ZD_DSMAaZt%keD)r_s*_GkOO|fyeuY}zcKT)T$hJpnfyseQ%#s1J z^_=KkVgSY@+qIU}7LkrI@;8=G(`g@cAqqjezx)n1kY2Vs(df#7;(Cm49?%(PC$pc& zbiIWc4Gnrvq^ivKv#aQ@jQ#VflMe&Ka#g%@x!{5PpFG07IvetC#=)i?DT*k%*ZHPE zy8I7opY;Ohi>Eu+6h`vQi!Lx4))p*qaHb-EZ7V4z6#Hd{Kuh`%BZ<` zHIVD;1MLE%vvN0iq%)~vI6TC7ox^V?Z6=)P0gsO#@GJMatBa`|J?i3<_6K@v-t)+p zF(Mdo1;&3q&83{FL6LzD6^D{Wag6;zSnQkX(Yu2{zw9V6&+^pRT1i zH^kyRR!^l_mAbGN(k}MEnlDH1B(oswBbWtuywysTR1$yDw#jQ4=qg;Hi13#kv} z(J)YeVfeVt)>`#gp9&hFkSX83uzg2VpCZCjZLoP)EH{$#pC*!2#tpDlas3Io=0o&~ zc{C3R4dYB3id{90+5BS~bBHWT75O{);D07*&?8!~AV{y|ZI9E+{opfw_WHGpcb4=r zE$Itll<~rF>ASz>AO07ey)6ZO@yP3OHr)0>Xz=roaS!8%va+7vTc{0TnOmwAhS>e{ zec{zZu=|JKQbb+iPIxPQS0+>ZO~0;g%>MR4OzBShqZKy8e_oj`;L6cwul7~I*`7Q0 zZM!j5=%yz>IMp`Ij5`fBU8C_4DSs0H|Le$aA`#0>ZBm}od3Es@WcAKQlo<7)Y$=hwh!Y*#{T_-XFWQc|U2 z-0DAfvka?h-hats8CsU^+;l8$faD7^DXb0)ar(iz^o@9#7;d+(2%QKCUW=FdaxQ}a zxIWg_)@_f0Q2Hy-8dEVap!F6XTc*urK4GOGC>@v--Ml6GV{jzzEsGqz@XOcq{G}&3 zK(D=%`a+V?+p`2n^7$J1F>xR?&f2E8iuE!ZM7x+ZKt$H^eqD^&-ZTr%pZPWx6?OXG zL$!ZRUa5`iA1EM%UI8dhf&_Xg$8c@5No_5`haZ(o6nqK!g~Oy*~ysdG$S35W+#CxtcpRO$TEh8h9WkWli_i* z=i{&fQeE!ANM?1c3F)%lqCdZk=R6Uj{6Tfz>y!vPd-n|mJ3G5q5o~wX)SIwUY_6If z-8xa2aj?_QJZyJ&C_m8vyqD@Y+rP-1nVH$6)D+BeHQ>mE&9xPmFdB)Oa4>tqG>L;| zr~mV5!90BC-St2UoYb8%KW$aX!!Nhha)v4sels077M9-ME^VwoQJ;SSb!r&pkw2^e zwIfQ5+nEtoF@;&K>TM71-y`Up1WrA<2X+{ZvUlSS8Wk*;Z{Byd7u8?$cI%b|klo&8 zb=Lnx@PYL>&h?~{bt0VW-i2UUIn*d4r-x*nbs7tyS*c8&;zY>q4JT;ISf@Nf0?fvT{*zD?MfOlI|FaH$G{L}T~&m~8N zRclLK@5J3DY|Q=+GA1qkWKc5?oFs29zTd#TbT+@Y(dxlcg+e75S-^HQW61CctKn`Z z8YF2jNVLyNZWu6tbDf34NBcEB)BKXXD z_dQ17HYiA2$=1DveE&PTx+hwcQpcb*Q|od`XqZae*Jx^%9RIrtSibB2+@0`gZkO%6 z6D^`<02a2`wZh?1@-Je3MN?;MAk{gtI(AU{a$d0J4xTst*KhnI>|r)OeoCw6zDgua zRW8OToh91_YDT9>yi$H|;oWnYvMAk?ETnx90l<`KToB;~*i-u|(T`2D9A>V8a&7+f z^e5Em+?VnbP2WR+UGUHINz$nS0%nPDF@K_6Vohf_k&1G$8a4qhMDvd?^qmRJx%C_^=5q|lvk%wNaD|C*w*T}Jhf$5?fw5T~f1Fyy zTW+_!c{fW!?({fh?-d!Sk>pBpo@(~fEUe{%Lx`yK8y_bM1j(78tIXpy#sM_bc1n&> z@YTMx!x?O~Xz|F1v9^b1qc%4F{0HFR{a+s`DN?KW;BXSXlKysxIF=NVX&6#rdf&kR zeahxeqxEFPoilfU6Y~i^yi$di&F=MEEC?7PTuVNN_hW3MQp6{fYUZyA;ZPwq)z)$( z&!k8$vaIi6n?(;#2lauaCz|GUsq75Necb|d{$zm zDn~_mr4W{ER$mb|L%Ahr@6i#MOiERy$5py7?a+YC6ylU=!82}Ln7=iZ5)bv4LBPKtEdVt4FgGpZ_Jyayt@OSPm&D!ZJ- zXV{B+UT5vtq~rTTME$s5{`r$JK>g`--GJl|Vsnr?gZ)BXs#}mN=bJx#rgaPfE6vD? zYUBK)%c!uE3^4c|O=;$D%vv)3aiOwLGLSiwwOnWWBEtJQ)r8Z<{gN31x_&zl_Z)Mo ztff!THqicz-K(H^yJG0F8S8pZcbBv%VRqzsaY$I9@P>)dK~{CQzxX}YlZ~k=5{v?D zZAl$hYunrlgKb6U<}A44hdW`5Z_gsPRX~v+7|jXCAV&#|LC%3MdSPt>`z;aewj*ba zNEY{sDHJ|DB{a7cHCSaUbHZ0dQGr)PNlbb-mQ&QPE{QPu`c4Q~swB_0F%ndS<_cB* ze>}_j_*vEx2-4a@3ZI*^B8TYZ3R`%(>RcOReRIkNhSqwr@`MYvfBQUG0^8}<10MXZ ze!R_uS5k|v`Q@Q&;LY|UM}06lv`yqVqW9(oWCEYlzzeF02${BphfVzfwCbs^a%#1-}< zt}TfG{J5pkIt4`Gc+T}#obSi)1Agrm>{19hLjti6@MYmjm)@_*l7z7w`?+OLe>~O- zbZEFG|Ie-^1yZ9EZJY>5ioD2jw*@>Msp0UPaQn<1)zfE+**{rmKJWr&*YzqAa#t=O zGi5FzN==^&5vE-00X@40GOF7SN(?p@V6&!@>H!a^k5&>$3Td)@a#(4wly zh+N13w6j{ElI4Ny(;EB!hSe_8BXX1&gg^x{p&8$;YEi#emN0FD@qAtrYbg7(E~_aW~9L?5{r+^4c^D2#!0~)dI*@Sl|cEFcV_q?Ew>g z7$;SAs2)PW=qu@y?zQM#SkhDntgaR=DjBtZ{GXE~?gg{HVFWA-5T_l2&Rt)(_uFO! z9DBz49R`wLc%Z*Kqgk*mJK)MRPY(S9#{Q!OA0K;3{3(91-=BBo-9X9=ha-DjK2iihw%K3x!{_+XE|1=UDCkK2RJgncw^Xt03AFVLj(jvB%u2q%mCe-hSu5b<$)|e z-Gw(((98YS%V4FM-~N6oE;rq~Kv^$*WuDO&6y9ola`^2h#a3{_%FR6TJF53ccq_el z%}Z<%uq1bZLJZ)mXYYZULyWSqz_=%S04a+3j4%#BQbw4MXliq^9#ur!_bcuH0wu77 zCmx@-UjQ`bF?%4pSL6RBEf5V+XKfXyUlvlO-F(h8hYdLLX;~-=WgfIAAt}Q_cVMbU zpMYldCvJ6oh|#6vwo)dPk-0zfzZZ^%K#(0ofbKbWBn9=C;^xl#DLXCii}iC;C@lI? zlQb>mC0Bq*!ui=xFR((JMh z(%eyx&X-OBRpt*OIJZ@CB!jlm1#pL5-Z3Ci_5hW}a;To=x1DE|NWX!9^b88>JW!nd zdFLZXj0lW}3pk&OH{JPe%iVb@;=xa#H=jEM()C(EZ>_eg9z%VjZhWvE7qa#uTNqtE z5dC0VBASxOg|PYZPJNQSG;otf*9TL{NYDvCwp5#**EON(DNUpye>=y934#8C>DSUMTuCq4W!v4sxMUe3_Df(fTgt-qrm0de$!R4 z{Nv}BMt|gsCC>kv6ib4QSJ_W0)+dkP!Ezq(@#S?Y;i1o%0X#HEUn9HH*05VF<-nfD zYpzV(Gdyxs%O5wQ-55>3K%P!z6LljyXKN48gGJK0-_UkfO*)_(bv~SVazH@kLhBx+ zd;Xww!6xWCY)}Ry>u=}fY*lmzJlHs-4t}L7OCA3kSRihNb%1p|5|WvatGK(%F7w~V zSBDIK%IiG~Io)`G9w+m~BzHxE=^$`zor;vz{d zK20Ay&)dxri%&)9n36Xi0xQ?YPrNg%>Oq}RKhK36SX}GV998Zleos0r5hiE(`3vGm zC9nPTVajsZC>Md}SWHAeC+%|jgi+tQ29M9Cms*$&icHq;#l5)whSxpO(4B2Qt|3ZB zt{ha1S@LV3xOSSd19~br>XF#(8xHGzw|_NV2=mCKQ1zGsMMNZSq5!=S6DdKo@;XJ& za-elD=&PWLriDh*!%L^dE=Q}8(hMeqR>#V@7FUkC0PpFfdf>X-aC;M2t!>B>6>k$C zKOd|)Kf4fFeeuVsBB0lJ+^Zg6ch&svKql}=zR9$)w3yF$RfnSx#f@@uO+`q`^j|Totz(O zO4YIN`?{=Mc)ub}y&!H7{*s|ug9w2e13P0RIN1z6LaL4*C?%EgxVoP?D5s{Q!5v`Z z=4OE(?`U|LobtuiABBe0DagmZG$-fuTDZ;o*?leGy>0Wz%wU=5))JH7<26(?j|g;6 zNmMwBCLDg_%ga>GivA~FsRQdF^R49{>=Kf>;2|!$;q&_Gv4dO-Q(w0$lt^4CO=c+< z((*`Ir=HHadUBE8*xI>k(*`PWTw8-JyX0eC*^<~7(#+;7o{`A2HK*!_;-3gZFD^6; zizN_Y`+xwt22Oy@tZsV~b75?B#f=4r^s8dDmkJZmBH0p01}Mu3PXetXpLLp<-1bYkBDw!9Z~l>@=?B^VV(LHj z*Vrnd>L@`6;gt>F3DMn~u@QW5er;%cl{3t=%<$>nF<@q9>9-TlczRy0`>nw*Pr1*J zWOXRieK|BU<{kz(lxKnb-FlU^hN#_G`qADrh#?nK--!^;&oRQlogW zK)Jqs`+L43*d@gfO@2Pj&;TGMz`HtqV*2sjMdJ3x`rxERiv7x49Tv_fHh15OKLy^@ z!NXI2Z#&QbeCo1(77kX% zkyE7N3GNC<+mMIjNs@lpCZwgUM>{XNhU+8VP2a9mX8ehv-VR2sAwZ3e)9H7tiiGpA zT0s&Wc$Q-Qiv`DpA5s0w(nK732|D-b_ETTsy@f<$-wrCz%rVSBR!CX`@!sV=P@oZm z_#NMAdvFXro`z6B0ONJ@mD}f#vogO)8SG|9L0z|o1_7_Wx7DxWn74|YB$hJmdae)N z{3*G#Io&U17HPNk?h{Ach}{4Qt7(rwuyhpKh3ob@n{mTaD$2ZTr=L7crhND9;O$J` zec;I(ZOZ>B?ecW2=Vzm?M_9o(Jfdvku=Z;g&bwM|imOmX-rc8pRdioDbr}>CzI9|Y zD>n?pKO(ttdXn!hBy38kJMsj6^xfjT6E2M9o8gkfQG4z#Bp!IArfy=4oLgzoX7t>0 z9|fOG1_?ehxX9W3d$+}cVYrj)3e%t=?xvuE% zow+CiI0D~Bt=+zF2tSB_!JiPmB>KG}}@HO~U(6B94+!k&ET9V(t z^UY14OYF*ia%^JgiIw>pt7u=Uafa@C4|^c~bXS)A1%l-p%1}!x+7-8^sP!+%4noY* zbI}l983x{1Cyw7o)4p3uCEqd`e`GLc4t|Gp-b?IpE0{4_b!DsC*RlBFehVI7)?NQB zg;a$)$Ns#z_qBV=;JDku(qp97MD_P1#1a?Orb|5aim1J##qN53pB;%&vf3M3l6o9v zsh@YXFXgcH1LfVDmp$QZ41Rlf$mNR^d&V0Tomh1h@r9>vqChnO_OA7|+}>KCvf3l^ z`bQQgy1=r(%>20U7IT{@;C!2S10Ew1;!dHSrCRkk!O?pxV1ePM#~VVVN4TTl0K<*{ zL)lwKMcMUz!<2MLC_{@hNFxXgLxXgPAl=>FB`FRd-2+I8w9*|ibR*pz(%tWY=XE{L zr~AFnXDt>VSgd2sF?;X-{>LwT>tTjCD@`^wCHfLUoA+k!L<2G$N zuPC?|4^kW*eRXz6N74MgQCx>?9qe0e<15-w?FXd3kLNNg7G?M$+dT`$Z2E=T7j>o` z{RCy~f=OL=dsFReq;_#sdgGE|)a{1^^3~gZLF_+)MD5p;F5XqS@ftkBE!=3 z@vrwNPJgDHwg;i%oe$v&vDF*oPf{b{`0}~bgUM#Jx2m6BYj4$p`sMYul7`h+d}iLu z_}aVZof=xU+}_OK=;7uBP3 z%L19didiTsD&&`oOUz*qDZR9*N!PDuHJ}1ps+=AU+_dauo;Q=hz4xJHc83{`vvBqd zoBPWtZhy20QbJOU#AaKX%tQ?+D0}#6mRMgtw#herOo?GJ4P!|Ag<3lv@aAC7U{Pb#| zsfJT_N-$!Rm-}KrZCxFOVRTi<2O)%9s7O<+lc6kM8P>_E&N#fvwhhH0dk))(RA=P9 z6d&;f>~u}kwnnxLAi^7#SWl1%PV&B>W`Bwcz8(1%(1L-?Y|(hwB}C=16!^(`W5%^( zB;l~bqm!za?i0FN1j3?9+@ZGB>`DtrjN{&@awXc{4dfN?KE2?&@R@cw&Auym@!{~p zv7`{u1jhN^rf++4U%*%8_5+DaUySG;b>V5pyv8BW;bX~UFJFGT6@J*o$2Jx~C#c?n zqZAM*%(%^Df3e_QrU4H-6Le*zFzv<2nTjRiA-lFEL6opBbp;{Cc{Nrb~`tHe!>gal?z!zcY2~aO)@`XI{iW=@-$ys`=fBPez3Ki zYBGn%I#AM_cZF;kEp0~g^2{yo4D-~b=s?U~*9z4H@a_8e*6^b@PF2Lqqf zpl*_uY5h)CHrm!FvxhiVi7fR3@RoD_aYpU)vBE!Hydf4b4jwN44R8C#z++8F_mmBZAnVa>{Ng!frWhDDvdvmfGA3H1CU!LkW z?H`S!f&3P+6r7N~33r1RT)u0yQx{1WXPp*1&#sNt_xTg=;)O;b>+$bKQA4z~$n|?~ zMpfpOx>-Cd3UbUT<80s<>5BQ4X2S*}h=j%CukYU2GA>1Jv>ce+Pn zw5a0FnFWTGTRsm4`_bCo0r_`79y1)D2@F3iu9O5#^|kiZ8d&7(2aBfdPQBiHlYA&W zOQz&{%C}`J&MCxkJw+1@=9*hiDmnP|T3*aI-gb5Jzb|y zdV<275npJZNF|P)EQGkD$?6I$Gdgmnr^Y7XGReM)_lAlX)4s{6QyEvLUhWiUAYm7f zx=A}m8zRB_97fFh3^Rh3nVvSyh?i+KU&5FQPQuL4_(2&nOXp{!@`y?5gJGiea;d{f zt+ht$(F{2{SXwgygQ?&{sJ%u^@ElE8auLJ?S5bm5F^z}Z3 zC)ihvSt`}7_?@YTVH9b~39?mF#h>ae?)gzScB5fH=T*H)jX!oe++{B4#Is({le=#ejp*T-FN~v6K z4~G!Fk7oxusme@9JpBqzo<7r#yqioo7DS%{gP8?tNjD`cVtA20Ab1z@S5 z=A+wo=lZ#`cT5M|n*{HXk?DYWV{;eOZR4in1*qAXo}9f9f;9eEs1@)bjaB3phXhe@ z$iCAUG0R@SLo;yT%Zt|+sE?-}W%vk>65QX~<-+#xkX>PgZ#nD`e*I+fxi^Mgb0Hfh zo(qCx8~r4aRmq@3E+tHjo2wduGXNiTpz)2H=Z?x zH;0FLzVD$~BCR5F&E97_@8tLirZC~Ct0wBz|j{}MJBdu8FU za^5n@F;hak9TZ)FaUoqv4idgS>{!TwKw5L+FIeOfeKXt7S2MoIb$hFt1n6v5WTKOt zvOoPgx8#V!u!Ax4qj9%1^p7W;NpJM8BtDABdo|}aYwKrc zwM*sb$Fw8hv%1 zU7VNE25Ba46qLayt);&sZI(XGKH6WN?4_cV@=;>SF`PH|WL>|j;@R3A*)TbDt-Jo1 z_7^n{Fam-iVsz4Z&Tj$@5ZCJD_h=@MTcA4!*~|BIMq; zQgjvNUjdI-mF~>rQp3XnQ!yKOD>7!(9RNMPJ{CobZ=77hrRDvJuenE6*^+;DGP@D6 z-*Q?-!O?OSfo0`MyLh*u*$UHc-rAnS1FPYru%YHTZ(e^~fbAlJ2F(hC>I+!!^KodgXrt?O4@#I*B ztdpNc`KtOv7hRm_$PPdB_(vbx$#TpV$C8S??|U|;b>9ua07U#Y@96qo6!B+^FOMm` zM5q1k)I$*#_l7%ox0%6d9+x^|Rws8Gv!C0d5seu8II%U=Fky}sbJTmPz3jGZy%1!< zZ^YsU(ooNM27yVwd6zi$hf3exWtHf_X=p#MN2CBx;k*Q8p%Q2R$pRrfjW-7auHK%{ ziSWgX9GP@EnH%GbiMp@2DRH}2XSRfL{d)bD@>f~k1#S+`DA3l?mk#SPc=O{9w9&z@ z&-5-h8xVpuup*5euI=WFc4PP`%FA;*4415P0+M$xQ!LELa#of7O@f7YoPCuCsMw${ zR+}M~hsDZos4FKej8le7^9TIfo)CK=zmob6v`$Cdl1}~d2zts5T&Wv6xH6Uxzw(9c zxuRBa;e=r9iQIGiFn*D%h|!1MWtd*mOak{t-AV<HM^L zP&?=Ftk0}t{we3fS($&{H2K!p2{^n~j0zJX@fwFleq@tgzw*=5eL*T@o_Zbc3Bztz z)EB|KM?O}%K)(DqSPjQrC?ThlQ*|AeAW26e9Bn!+lWkc75=l72ohQ2?68ns6ZR2%s?Y23u<7h}G% zCc*djLKj_1G`c2^722=1h$VT3A|h~}=oLBvC5p-Ws4O)FRp^?tE*Rc_NlqD!hDLLN zPh_7>%?D<)oF;wGh%*5EIc#MCoM2I${6h#F8fs|aQ|-$Gc{ABwHS6Z5Ld%7K*d`Ja zZ!WdNX-*R%jLIA=YYfgibP-oRPClBpzdWJpl_ALzf2qaBfGFktG6WRU0e1#LD#)+e zKXlh%um1u3Wm1!A7mWp6LEb9k7yTc{Jin?3tCz-1?Y$(h!kCxOuaoPMAIFnM0wN z_u&4>=vVB+l5E|#WbINbV-W0+J0EF-fRQ(B5_N{(9Dyj>Pdn8leRylqZC+B*$!>VKcKYOnR+{)q(hVjrvEszb5OV zWSB<6Uq#BdF!Bt1OFx}o2KRF65s#Po5Q>%IS|2#+sPbn+a--H}iE*E=UaAhR`^eZK zZEVN7(ApxS{_aF$>x^C}p+A#;6Xn&0ddBgMj?E`-+nobMuli@{&gShj^?AWD+3({?Mt_9XdW5)F5N6%J+4OEP`-Yv%y5?{B1vSK*C>fgTkDnmC- zZV@zQw&FQ*UcvZ%`O2eZ%q&MpqT%h|fmSClC=@_@{VrUkPED4Jsi2m!x0f~~Z#@L} z&E~u5;Ah|W*w2VNak6=zN(s<0eVh(c#SY=fnf>8!g%tlxb$rLMOkD7OW1M3=M5t(8l;m!1Ba{%bl$7Z;cw5Fdse#W zN^*jiNetFHzxAQlDi_*P?#gV=RLfr(344lflMNA1awAjfF{A_|J|id7*a*({jop~0 zZ90T-<(2KdRDKm`)O@5a>@Z+7IFzV?ErSmKRC_!(xa!N5Jr?kznq0N?B1M#52HCLN z_!1IE-WTJar;>!w$nC4WwB2BnOk~)UN_&6}BW)M?grujbfw&o64G%MT~y^JtK z7&JSpmTMIod55cM@%-*CcT9jxMc z48+S69ipY!%!UY92d@tVRj@%iNj>kv#;@p2y!ez8>2Yo`oF%W?5GdFMmpQRLa85|} zf7%mlo@8L&wB|LoEWhSDtNSxUPIllNvT3dPpE@Lhu2n7CFf-j{*gw<@XXXbs1b4Uf z@+{dYtVLeT`}x7V>b2nd{-#N^-)Oy2|-7(-6dFa9VIMWhI2WoxCmu&*FJiDi+J19On7jYZ|Zsm~u3tDdk(6zhg5KD2SasN))w(iYC?d z9FkLGQ{X%&^{a!)RCr?H^m+&-qnUt(dI{UXG(0vX_@WA*6qp94bb*>hDW zyo6@2)_d_X5P}bwmcMg2QcqND-Tj@k-)VsaDxu z@6N$#?K6AIz9vu}&5{Okkxq--3zky4-XD4X^PDMkOMbD;A%F@j9)uNI2uNso+X3y3 zLcUi<;btO8wyE;#Ur96N2%Jy0O!cb|XxCXRl{4qf>2|`(!6(pInB%BW!Ver8>h+}h zFt?XJ7Br!l!yec1#Lc4Ehf8Uk4of0bZGobA9^ocr4)1#Fj(jYHQVk;PN8w-n z+tt92`b+E&)MwRhjxXC*07t4U> zaM@EQ>`PiP^+w^RL&(U3t3iV@Y4wC1bYlvM%`;I!kA9c&waR%& zhH-V0cz;nsl_)wU$~SC8*!{XgO$)!tAC&m+RxeZ=^8HV%AHfhUWs(i& zg?rP_(O2J!Bbe&e!H5^auI8kxeSU93NJRj_qI5ikUa&Qz*X4S&6g8$!d!DoYdi=13 zxgO&5sPw1Em|mhZMwJ*Rp&FaX2?+k%mj63ggZT}e_|;4++kB_> zRgq3rpSu|@0!3-br&RWD@u(nk(hSQD34KL8i`Lv0tm)pclVrJsx|%D+(G6K<ujL^qy@Hf2b9@TT;H!=3?)zwaS9LOT=@h0VQ`@2&<=A*1X z-4L=szXBwOZqXgFix`nphKkK!_3r?)!u@AHGMI^~C*+}b9A(w=mZ4HujU~s=#BAM# z{L61FCKcw|QYxvxacczq*a~3vM*1x-U{f=KDK1r9mHL5x0coj zP?@OX*PY0+RIYQtowLz(s?IW{BQz?H=n`5m_`ZyhdayFMaxCu6#f~-zu(~KH!^HlyBIBLH?V!Vz`WAhMx<0^|)mIxeIzrh|-FAqs zG1FnX_swhs(KdBE-ORt;k4 zX;f;R)&O}&6((A6djuO|iYk!yQ}pEV%y!579Z zWzs7~9bs4c@#s3$`YWTB%CC@+=UUx(Z`*E!usJ`4y>#hUZB+tQac|b|FiNj(z*?wmi|mz#aDk5P-1Tq_F^(KV#N*AA@Hep!672~c zR)Js6J{4m$7TAD-ItcTIalHyX;aG^#)?jsU`<7f6TVaYjF`-2*mp^~@|J2pb-ptnM zMl-!GTq|h0i+DtM=}uZSTTIp~Sb6&d^2?FLL{b0DhP?+X;WQ08qi_0z;`fq*75D3R z*@g;rWJ%(M>4Q!7eitQ`Y@B^Qf^M3_*gP|GgCw$`D3^9%phW_a1m8G?Tyz4m^2#VQ zj^9pC$%6c4VA^f_za*i|AT4zE9`funrpHEZRw4JcO=@5>Je=s;>b@L>SWMsp_VSX6Wqxd3}g zpagg=P2s9GjfQB};0A<>>dGpm|0FA^NQkFxX*NGTMau>a1{V@IS24xyH>BUCJ5@RE z%1Oh1iO>F>fK(7zOn>0k+xGDXI)0FJvPn%qgiLqNneOzE=O9sb?UMryI%bAEeA5U@ z1{ne0XWLXpkPdy6pA-uWKtpK9^!#>odf6pH0$6mp?JU z93IuyFC4I#(ug4k3i-Tp9zL9XZvMH^F>G-hLxmW~@0qD9H9V!T)%tl4Z0kw^(Qx}N zs$-z_K0%WHR{J;1kaH0)A3K{>++v}2n7*^sLhm;=6g|_CA13D;PX(H8#R0o;V;~DL zuHJ^Y#?G5_9%$rSPrJoiJts(M`%t$-h22OWtT?1m?B8*8N)}7&Q+;&zD45O807_c@ z$mtBchoa=>r%B1Ybh4JjiO)2Lz_9e1@ROGEx1$uolG~?E@P%m)#jP}TFqA3~dDzoENb4q2 zgmFai;kW)W?TueH5a(;`7jwmrXLt)utuL0N&}!9%l4Jz^W{SigQK^D_MWy(u9A%8J zVr0D^a!tSYsxwAEn)p5ZY(qGD73wT(emoVC88;*_S^Z@JP)hQF^HX7b@&F9e8ayl( z0F2J0cby76rH@^PaTl2I(jsoKi*Xcl4i%CluTK85?(3zI+O3+*XLYBxa{~=TH~blig?UhRFsu&e+3X)7o^ ziC)${|3j9~Uj1$<#J>@Xz3EgnqcKs{?~{~)idwsyng{-|xUT3t74G>Gi2IL9CM{0o zOBa^VDx26MpvA>{${jkIdrq>5Uo@obK}1SIt9{?@%ZpG3nHBaBJ@H*>PATAL8{bCX zTa_@O~`EP^wEKML=dr`3~g z4|Y19GLEL(h*n;B?)qY3fpuM{FWT=jg9ue8s@^G7fhEEIySm?{)Ua?Q))>z8Qm_%x z!qMrH>`RaUb7XGbZ-AlOzs<~WO_V~ARrd0)J%fE;6pNrS8g;<^@c_#JLsqOY(J zQ6-}W$V({!J1hj>q8inLh{Q$fisqD^=2C@-K>eu_Rs6kLG5|X9PaS3WWleL3!O%|r z&c#6{NoAgK@RH>{dN%Z_*&mqq{*kk(VQtoG)JoLVfINChIxQmvUo3NtOPwL31x{+6 z+epaK?_8D-72^**;=dDbtJ)wK#8g)LM+D9sGpED=QZXaEnQ7UnLa)v8e+Rx3TMhi zh*SSX$VIqQBp*Gsc_D)ZI0vEgMHoIUn+L*74KYmxllGMv7oXsX796L`khHOn_k#O` z(y_6LaGcv^dK#zKe9z6C916If+nm>(;2#GZ@6$&F6rghCg zWLkXe#j~MWTej`rQkL<`{7+(YvEf53dAF+3HUNeXLIz^CYdTXGb`4&~8meI_YaAu( z;!x9u*a*Ym=Zd4e74FD*sxw|1x~>U)cGE{PExZB2)x<%JOI?{5+Xq0a*e`*1(KJan z5spy(SWVd*8beeqk7&N@2DeVA8wg98yi+TWK2^{RyQg@ur3H~WRv+Pc{&{fe*gY8EF_9(Ys)mjX0#$BY_k)hR7 zl;!7}^tF5-M*=66M^UQ@F6v+Z;G1&@5)Sus8+~!$15)*qe;Zfnc@N5AQj+I4Sxm7PbHfBTr9z^FKgT5o7A| z?~=K_A6DjBw?MTf@pFe)1KmXmi1nw?nrJ69(+E#7T8i4e>e*|R8y^uBC!)vh0e|LF7?(QI4gh?&N}JBLgwa<3_?YXhN~p?~CclJ=M=K&)E|LFw zyLg8Y5W&u9i2p5#&7bT!API3XcV^J8pm+iMQjOR zSOlTR2O>DD7$F#E;h5qhGq<}B_j2sU?}_6Hjfco8SEnsGcKReg8Vx<18$rdUBUTog zQ#_Qp5SZf}Ad7C$s($xwssj?s4r?>^}RibgE$H4)I_Z2jPP--a2i;s@9 zam7D+nF|@w#tR!8f-~~>c{G86dD9V!I5DibAj2*7Vs1k)aG7*j&|z2@ED z`QRY*@{6+mL;=TJgrWB{IUYS!zNaIFePfEI2n?a3JFXx@Z)r%S^wfV<&iV$q;#!D^IigTy=3#tTfN~zd)P{ zf~@9TJQ>00dwfOxO52kd*)wNgdT5JI;R)&gegry&NKEBCv}Ual)1dLr>~6xm?OvN{ zcmiyhZ%+fYB9%hehCjrv2ZfK2m8IBg*e-p+v9z6JZQbe@x_2F|-4%Dt^JT{ArTs+*-RWh8Y;Qce;yK(Jb z6-A>orVk~=W@5~liaO#}dpUC!ac9eaa0{S=c_+W$ytI^&_r0d(TaJAfuDN>>z<>h= zg-pyndBQq`VM>PSMk*DL1tabQ`7`|ls(m}XT>s-9hMQyAoR98Ebi(T&J^KOFhqZ8d zP^g6tqeed~>0n*d5#~C51pPvg6La*+76Ly4YY-Ce+m&G5?PR2%jvu*=eG-AW>wsUiP6h7Uz< zmh9iNlr_XoEpK%u%1O_y$9VLWDC79XLPtZ|Y3bN`_QKb6XVznL zxcQD|=`%lRRbs{tBwJA|d)tNYrf+!=(J%Cty%;n={8Mx2=Ege5-{)ktFV?f*5Ox z5L&6X-ch;H!+z`ar!3uQV!mX=y@*{Zi>*NdaOhAa@s(ZM@WSfht?D4o*oc!K3JBW4 z7u42^4a=JQm7}qiG@72{kzX0j3-Bz)-C@TaZVMF6LVvhrn1!p$rWREf7LCdZPaX@c zN3>Z5z6hV4N@cj4g>RnCg=mIep`T*iOf60}%@Dj;>mqxolOK@+L?B?IW(`YV2L{>4 zgckTr&Q`{liNbf|x84AR-ACO(aELiuY}{Ob@Ie(n&X2KD5h4`g`|I)LFB)vC=xEytK8&E^^e~BxlSxhSS+Hbu8yc(r;oa++&ck4f`e+-r9E4Ff4}k49xiDgah4ZZe@Nz zJ=&4=I6N@HEz9)>_j<|rUT}I7AjE!GvjpWH{C4dX>qay5@tnmrR_xbInNQFaOYWT( zfP<>KC31=;m!oKF8a4mXMtGI4(tDFI^m>a;l~^@%m3fEx9+Mm7!ELZ8kSc)ZuJ&*NoPCV>@ zqNYLI)eQMR#nqedINr~FO|E~RmTk##)V>FLG0J{OzqJm!5p?g(ONdxR3^u+|&DHvO z*J~P&cKnX|P*LV>UDv!Y_9kosjLVNaL z6jJLHJ8*EqJDS6GEcQ#!pijXDu~T!|0x2>rU}t9qmU+$J z={TnO_&4Q}vncFg=CsIA1ZPohhSTCjKZQQ$FdUIW|J}|q_{zRxxs(NVQvYmk3<71{c@(Pe zc7I8i-;*B+l&%a$B}U=BS|{|wRxz;B+0GhCsja5@;tJ>XMKP0Oe|rbkufwAh51wi# zsJRWqKse_^3Jx%hntJ3+g<+S}VC{9qEp^ZPcp?0^G{aU$c{-Dy?Oj>x z2NSA&NB1-uWNLI1sufuNegT$9{TfX8{~NvX8{G?J7gj9R*91rpJ9Ek5s(V+9521Oe%P8MOE5r{jV?g54rRk z7?C84W$1&(w->;(%695ud{b2xi`09}gs2(Y`Cvb#8QzlrFc1FWAN8|Ev?Q`4N-+BZzry&oH?=pRJ{~sPE_4u&t_w8p2Sp%fIb^j&ZCc%XIB83l7_|7^R ztpCFj{@*`e*2_n_C%QV_j55khX;b#D*Y&d){CZd0=Vk*gt3v{C1@HBgf6w7%6a*Hb zFG>xAmnX1#*KVgi-Ko&IMC-Si>4v<9GhF2nWHf0v7W-cc$p5?qe<>ZB0KZsQJzcg~ z3%zpI0BdqR_j9^62`JmV-(eN)+<7dtNr!b>4f4Og=Hpc*gAcGiWrP&x<=fY` z-gW5?s7xP%@w7z>-s{`Jlc);*{j-)IpOv06Xz>ep);;^=QNX>WJBPPeB$@l4dpkH^ z=KsU93?H8bO^UKZLJ`<^4d1Ojz_)bg^1gke;oRSBw{ckZ?-{`_{DJY&=8&v3FEtXy zgnJhf_HX0R8a)te?W6?0Vi0b*Y+;mk4Bq6{{14FJp7q`&7_zQ9={B!0 z7T$k(F2}b&bvO6tX!YOkRs&oh$i63#c=1VOccOH6Tcftx+Wt$mJ6t2P-mz=%z)Xoe z^WT5A!5)MX)fY%9=#_S>_34fY)<5B1meT!~E5>~Lm;QCrarFN>R`9q3JA+sgNzebz zwFc;B)&ewXj=gBxKXE)bNUSr}&bmYO@G4E$y-KpwqiwX(jbF&~f0*LN{;di?DmU=r zpM9VU~X zeK&)!-CQlyd=MrixC+^`NJr7CJXrdhN$C0gt|UtaM#H5WYvS{Nw#WWiuPjSKgYpr8 z$iC@ywv+Ju2@T~lnoQTx7bUr(3JPgo_Z9zpHnZyRL4t0@f_JB5x_~2y&ZE5qRiBM< z=3j#L;{nb(A^-u6ipjXFP37QW_zii2^CMA}k@)eYX*~f$qh1?`SF z9u}ZyM;?$ccm2&k?H(6h#s!%FxdPnR(nnt0=>62=!q5(XeMdaG*Pf=ko0lZl&}M1h zv}T1$NkWosp8Gf4h>X!V09}Km&O>qrakR)vy8oLI509f8d;p9p?tXgluObcrv4yXy zpzP+KWbm6A($-71`mLA3j0<0DIYnT$fBK|3AXJ_vxx_dU_+rQ)Er0_Hh=9muNve^k z@#vib1bv|yTKi?Q-nqqdpnvj*#jJ6D@XacAO&6s%k2!hd{(^brO-H2QHI?0lyMF$9 z(|o9QJzIg!j!u#KKi{QtQY&DRxX(ApJp^`jS3i0#CbK?^!E(wAm|ku)-*VkGs9KN; zG%us`OmNLR{BkjUEPWIWZr4cchFBmD!jTzTrzJKm*GT)7x`|BJw;LWt#zpoVuU6;N z?Zfx#Hd6ZlB?I>;{r~b~d8Cjc^Ge|kfUi}7*|=j?PIq37A{XSOAEaCjv@%}9y9&U6 zAyzpwk7W=z3+t6Eh+@BPlafl+1nAIx=N5q4-bgxoI?3}QcRuPZaELi$)BO5YOcxm@ z(r4$~V3#zvqs7{+rj=jNd>T*YzwbCWkec#O=uij~xt&!U$AZF-!a;Qt{0x+wG%&ZZ z?0mt0mcSwcrJ4M71??)gJf*L@%R7l2Z%p(@mYOBpJbwBwMv2SV;!!x^V@`%}qH` z6ktT>1vBsG$6jq0MWawF80}Q1Q2N}f2^)U0Y7wF3>P&Rj%v@0X1lrAz(#F%xg&Vrk76HaEI zZU$Z=2aoA_EI+B)`ChakD|Q34^HMnicfhTWv>ke+=q184^CXO zoFhw`x5o;UX3~d1nb86}-zud)wLSV-Dca`Pc>6{npdS`TPN((*G1t3b`HjYy^ZSEbF6f;O5&k;$B+MA9ryt;kOAdY1A$9Axav~ zg-Ay#q z4|c(xkz{NCbLd*hf#RKW!wcew>{5uj>Qs=#@-U;%j}%!YzxncU4(x?|%3M?&iy0Pi z`~Y|v0TLULyfcg%1T*Z9ML4Y0_yFpLOm+DkH}aZG1OO<3_htag7I|&^S1iCcVTr$s zcLyi|6kh*IKCwX`qmX^04T76*gbhmDHD8&LWS=1B8UK^8FiU04>{_|Z_aE7eR#|J5 z)l`3(1|@aek-Pt?=(;xk09RpIMAT;~t^MjgI0GCG{>R?K3#2E6$-KsiH-7y@7jisj zV?MgG^^%*m2LjdB?;Qjbu7a&f+)V-oRd<;2Ny+TDfYUmr|GEdgY3u9fdO+iXL9Uef zr}g#ecByG!;>dWXmt!1y&TTSaVQ3BbUX9xES)VG|*|apx73=%S&o26f3H{B)ZLOg! zfLenq;B;vX)zp9$0lffqXx*2DSp%K@Q>XQo;X9ujtr|PsEz5!9OSo3Pv(PJUk<~pL z_(ra6-RQ{uF}}y69>ik7#$Vbaw?+xI#=lSYz3g)C9;n=x~l);DnA z$ZCFF$)LBOYj$K}=A`az^g^L4E6=OIPa}dy1MG2g5uaThS=XIoE;+t8l|Ei2cjI3Y zJzaip6^m07YymTa*qLDJtGZ3t$*zPG*6#VZsOIxG4{c~v@+V&hojh{pLSl+-cSmy! zrK~K%*y{82%B3`MdsDWpEPf_<3I8;cd?FBQ)O(VAG&V;U?Y{}TTLfL0b3sbxC${%d zZCY~mOHW(2xy>7ROTM@KC;|=w>3T4M?2}%`QhB{0mg4e{cnJ!fJ?NZ%_}IN3JT1UT z$d7gkfT5`}xH8*E8KdxR3Y`v%Tf&BOHK;zeT~%~ZIs;)gb7%-y^2Q@iU>>yBJT_7 z1%!X*06)WRO3yX?i%w$wOQ(6jIWa7$8&Hzi!-ZTL)XZ2FJxjIOxio@F@0dJRkZS1& zu7q%@9TqXD`=6;1)|T7jsB8POrt{;zXG6hkFOcX6-T<06^W}ye1n;&-^FC{v#7N#Q z0_1sh1|q)|nVBgA6hL%_#C_oPw~c8IwwRR@nhcJcOXO&OKNShMeq ze3-R2J_>rl9q)$Ptt2N|BrJh@OVi06!aSH+M7yuw~u=@>cjuvfRrd@mgxquU8pq<&C4M{xDb4=s~Pqaw2zku6D`>xgBJ;yWHw2 zHS5KmG~PeA%VRt!VjUUSYdjYt^?4trwSi+r{cFm=C!Y(O<-K5VF9?sYF+T1W_Q-R) zn=mTBLqHG(IINW&oHoulQobqpshIQa8MzcXfzO~Apr^p>cC2#Td?0nb`TpAaAKVVkqx>4@|$`7wau5xD12Tm%Bd{zoh} z2gcGE^?72~dA3i`;gR&=ejHn_tF~F4HGw?=3a(b(_oqp1Pl8Z!oIIZY+Enk`YzNIA zeZL0o%u(D-4#SWB>`PaN9eCa}5NdDCjGOA=&6q@t$EsN^9$^F*AU`ebH{a}6?ke*& z0~#v#2LOfg(3K21;crX@i6mHZ7$)08pMx@@-mHYHWl-)4u)l}&Y=wRE zRX(2OhawEHww-5AbvgYQj=f7QAwh+LH zKk42|wcPKF_IIAO4$#l>9^cAZ9TVHA|1@m51Bjt?Z%@U=QwoT-du#NOkBH|W7U@gD z7uH{Pd|+hvzi&8@?*Cn(AhpU3OOakUPPw@)z*sJ{8y!#C@9PflUmT-~bkr^eX9@zz z16O`W4`_$YZ$;4F1(%S<_8v&(D*4Q)>fS3&*29E29rbe9C$h!Ss5lw>GZQ6Wd}>u- zhq`D83%>jvx&oS!qk?`r^2d`3;vJkaC8_C=J)zHl&A1JAERJ(RxLb|eEDp!!GG=n4 z`l=oLPwl1+2n6lsa<{8LY$jDNsMPrFG~c*7%`|7;C$C(tX819YhPw_@HvMQGvt-~q zNk|;Hz2BXK^rA9%C?9@lBZKz7jyvK9FC_9OI=@Nc5nCqJ!Q!Ohp|IyRx}kV>8K&X` zU~`L%eH81*0TG^E+&4=Ie70J9=rOsg-#y9n`WTr3nU?BXqsDnaFNrX6=;(w)f)KU= z$cHHGSyjCq>PGi%u;#z;DYShh-@GZsXL(3xIm=wt4t@p7+`hfPJMd@U;$mC^Era+V z40In=(ZqeJRkuzUX>BQ3fJuY^BDB|W)fxOg{uH!hSGybvkfr#zkb?6%5k!1r_M*R0 zp|`lD+gh8{q;ocaGC)4AKss;}&Om}U9_7Wr0eEFz0D56Jy9A%ifZ$wo0p9x9N!V@T z!v^X6V}7h_{IrB5o>O*(&KuD6y3~iJdQ2R5In@qfkEP3z=F&0YAwoNkM2l=_ih2DV z#W}k|Q8zAsmixIR3!a2l+a5XFH26sZvaTv8-Di5W?{*G7UrKI@Ip-t z!mb53s)tm&tVK~$nFH5@EN-4_L&V4Z`IyEW9{cek@VgL3exMCXDcN;X z19QZS4v}Vgv9*BJgZsJp6D`}yAD0!2(J6lxpp69zGm>3<6d!lOsLSb_T5TJz0@5AOly ztUTQnpqw{Z_{!EF(vpapEhoOL#gM7flAVIbzxHKZM{Dhgk^#7)1yJF$k8eI5m1|OUM)F1U@- zr`fE){H`tejDDd5wfNnTxB8Hv{oWQWbQrt5TLaUV@+!Y*RU!+5@vCKhoT5{J?0zl_Mmd@e2v}rEe zU$=knEQxYI6ZTFG6#KMHh;1K<0yR6ZHoBSqd4Yc>8$Ae5tEb6DOW$4mv|;6I6*5QjgRRr) z{(Z9j=)iUDZRQ8Uu3*k7X6c6a%mfEA4(d8r_4wJ4(EVcdVL98)G z*DF9U?}BFN6$N%7;B6axK}!wA43%V#15~JHaF1I0!zMuMHwQWRCYza=?h~B3OCffn z)wbFffS!-HDFgMTfa}g0@00=Z1wbNrvmwrF$V0VUC~jFL!9IDmqOJRxZN7cLZj?1> zZcVUEZdQHpS>q4ZM%`>2Z^5r{+l|tdgWNd9W7j$FgQm?K&q~0HGa(SbXD%Kw z6yhRb-*Zm`$Xi>LmiLNT&5=VMtb}f}DE3pe(ywmbL>-X%Z_wFp9+C;OQ}zmi)%!y5 z4W25OHovyTjar^kUI{`kF2#(-$P}Kn6?yLgO_a=1M#6Hj-o>3VQJM5lqnjJ`?0^5k z;#xotg_I@`*H-)Liv8i|3BZ`Juyc`Fr4^Cr;>hK7Z_9EXb6m|L#O!iX)cHY@t)Tzm zEzgL@b%M^rN&&y#pbpJx0Plf$H9Hw7UsHM^qLE8lVj+dTh;8AwZu5bY-4!D#0=&Yr zq0)7>xH&-3xJC<&K8nu9UWbzW{AUN*_}X|2>+1c4JGJzkc=+@JL@O2d;a#` zb@x!cjkVw)!YIYxjM&K{4ARrOfFU6~;*9#z;o=VG&FwN%UwE(;^r_( z)%bOnP+|3Voo|uF{$}ZZ>XxpKRol!rboUlWZ)6;?ON3_r6Oyb4TEemq7%3D7zvxAO z$-$+<)>k>j!vrX!1pW4McjJxI-rpupaCVWeMq*dnwRoTy+Hg?6`soRqs_xXZF5dhfdQnY1f;uLq(P)xO1g&{x}-$9 zQ$o5y1`v>t8d73lXoeng$bmcmd)NKWhgqz1&ig*k-ut(Uzb&T`?@-&lbCoMDwC7gX zxC+4#|2S-H1XPTs#XUVgY`=Cbf-r$Vfn7!;lnAVC6q>gARY`V&Qj`+nSnCaYuJlSH zTY&XKvv#xTh>qVjo;#@n*5l-0Z|?I{rKs$C{NF_#6*1ZpfP-*E{Awr zcz^+wjj@WKVfjbda9L^&!-FM3r0%LGEb>Y>TWa=*3(^1f^1Nw@msLl{CuNdogv|=N zvN$(guV>zcls9-4;?K3Zr^@$3 zb666qVCo(FCwtbHq0cRe;U1%0`c67F(n*OdMOSi>+PoB}MURXKJh9cRh@IyQ!w;F6 zeziZ<;Dc?NIlQ9;NjpuqH=1>c*AE`m^KGYD64L0*KYKXe;7EJ#^f6wNZQR4Rzn!8I zbWDQbo^Q4pLPCSDPu7o{XEdr(=%EW40&XF0dqq6a0<`+CA#?}#k>67_+IytZl~KHN zJ)JwIdb@(<3ZB&)c>@8)UsE~qbk0s@5W35CQzi{RJ_H5!e;Bv!hC{yY$h{HxESm4$ zJs(e}OxqnN5wDb#lP>tt_8wg7z0WSEzw>>ySX8&ewgD0OLlb43=)W*smA)DUWbr|y zD!)3>Zm6h8^~j6WwY@zfr4o6Yy~vU)65c%_aXtn{e6tp1RpFy7>c==#feT&;?SPbU zF3A1QqNDcSU|#6=%u>~g1`i36tND31y7WCsm<$jVaDws@fiL5@#3agulkg|pd+WRI z`&tsUAWG-8=kGomO;#FvF_Z!EnKtxpmu^ZmgY)>J8YpRu?VW71*m7-SCRn?^h?M0z zw|ZRY|AowJ7YNfaaX2?qNYw~lBQra@puAsGNiv*EdMN?E>JQYE?kP*Z_^tdSCS>0{ z5f`4}PJaF_rf(@{jE>;NUuK8`)`1ps)t+RYm&%Ro#knS;&GJZ~^J1M2s7lf#r=&94 z!u$F=QYgh4V7qBHj2^;93>u;gHt5>i%%}O%1RYAg39c_~6-`HBr-l?oc=|>_4gX_w zqBC9Tr}!+hULt%hXy&9lpp0aCU0(A?1U8Kl)`V5}q*z&Z7m!8)l!F(m@36qWgWL@he|Z;Y&XYz7~Q|HTphV+$Xn z*HEX6R>TlAA7=+1sWHMjXoyAj-v%xbhu@Z`?D%MfGF z^(&HGP7HlR2iIxhB$>tYoP@iM(goiqi=og#CFB&o4)udcVJ*Yc2{hzz4;!a3>pf5N zCi=oHeuXCCio0u3CEL27cVQnrPSDdWJdnmgTQBBa6IvlEOJx6Cv;;1Q20vC&Q?Uo) z1W-gR=hS%`xAkY^wFe0s{PF&(q&)Qe`BWmsoW^ORd}*oG#LpST<5JhL(tH9^&o0#NCCY?m87pO2FT)F}b{Dm^RyZ!l#NntAPoQoi+xvxlyI2>ks{3rzmBt zjBH+hhr-Ggeh00gWT6?|izBG8^OG`~ax{?dBtG+8)P*Mzf=N@R#^$}BTAzoWQ1f?B zfJ+<%+Hkd`J|GM^{r+mAZL?UEf%UdNdn@iFJZ^kO>Q;&KQ0rGizWQ;Dglv9%x3IbL zpj|v?O^I8sD%a!`4SH}^wx0<6VY0Us&~@z&tTnm))311JxGPEXWw?1+8h2*$|va=;M;23u01#_b}w)vONIDXO#9j88SN^k;?D? zMmm2HjoKU`Ldnpck&om|38U)l2+ngJrUP7jm)?y=bfV4zv~PY}W7WOp?`!=!@@M1R z+s%=4Zk(q3W&<`;HW6#H6oLUfIcHia*EU7!eGbf`V$Vo!tug*Z#>j-tFr;tJw>bRj zY*#dRi}F=u3D|1b?dm$9L>H3DlQzp;1irnYJD#W@ynXA>&DqR+1-8F1JM4DxZ5v&} z+YaRB?CLbT`74RWY=b~b@hyNv^2F3-iB0vA$=vO4@W-nh@vCH49hKA9e);aAH$CRJ z{<%lW3iGQ{Me(L**S;>p1(v?XN?Bb!!F2OK zm6eq%B%DQfw%NrdVh3+%VlR`!wy2zvZ!f~v_pa;@03ci%r!%CmCl;fw={YD7#s3E3 zF(v#md{!uk-0k3!y40R64Ly}s4iAzma!R5yKG^Q~H@(8Aaaj+V zeSmi+oE$MJ0DmxfN}%^&u-FhOE)w1<^~`+s0=Hv8aYNUnX3Zyd{hXSGhcCn1n-wcQ z2Ek)Nt2;I#qA z*uk{$`&R@@rKX|10iEZor%*$}?Re}Sjk$3SrB#&bmEpVPywTo&Ib?A z@<=SrM!X=>mQHy?aeum$2wBE*X<~wfB$)Zufi!mG8=5L-Pn^;=|7>Ntoa z$uYNgR}Q%ZXe_3MS4M3MtGPCe!3IAnmB^He7M7ml1<@twm#R?%#%m zk+^>86O5J<1C-pz8@izx!a#WethV;Gmj9OXo&QC|w0pO876u_i^SoFE{`-}c>SzTQ zEL>48^w}UpdQd*a3Clp&!$zFE{|(kGnkPSgxwo*iQg5I@>c;oN; zh)@bPc1nvz^o6WlZHmC+J>`J?>lX-oGdn;$FYJ_d$$M;#juyffMH63G6beb~jZ5e$ zl8?R>IOmO8=(rtMr>_i88^y`{d#x5-%V0BGq3w`?R&@fF{M%nrC9r1z#xjfPUrBtB zFZtM>gpedrvgi$|<|$SH=BAIThWI;fCJ1W~(7w>M3o!Y*qLhQ->W|eE5zY&63z=fo z&m(~-2i5Ajj6WIo<^}N)v%O2b!z2`;KQa)H(%t}+-dIx+0X~0mwWLRr>HBpt#W)E- zD$zHsySvsn|A`VN2mfw_q1yDkA69k_7{}3q2SW|buuw!Z`dUiQQ?D7OKI`cyBh$@S5y?QCjE$V zN)cU>ihxXsF0;aB`>-xnmN1o8vlW^TL#^0ZkKn`-PidG6TJS-0O%H>U9=>J&-Ge!p zIBI>a?&4yYeG`@ZpDq#1t+Y<387EhdOh^T9leZUmT~Sb;YXrGX63}(_)UC(UeaMO9`b1N zaqv6-BzlRa{I&)*W%3=p@(hwniA^s51T;@YdD7mp*u3U?T1r(l)!v<~tQGm{Hxr4fYZ5%7A-0&PAPN*!&${Wf>-DJgkCKOZsXjXY= z?b`t9pKe?I!e+n)5I;37pYyGuo%n+RJu6 zk#lTFoD?R*%I^|rW}9UL-jKpyRqv44h+&$>l1QddvBA-*J0Hr;1kRq>om;1S=9osa zy|FY7+jfFEr*$0k=`c&^Umv{|ax%!{nQg|X!0z*TqQ8D836VH2HT`zZOUv4V;Nc;~ zrH5f{uf%ZxD*|FzzSVtGE*>!`rJArl*+gH+4Our{tk&^Qh%k*?sCAL+^YFBP74W3Q z(=r+$SR9}#%%U3T=5A)_@8!N}WE*k^3LFQID|^R7RFz;cl1#2GS|^DBV)oyj>{6%x zcnOeF5)Xf7o!fk$M}71k0ji6?!0;F_R}=9iI<^*|(r7hf5VOH}>~6oC&1<2sTxsjW zyUho*Y?;Eqad08a7YUolJmnOBKYAkk7OFD71(o)?H9+(^p1+)3_+dA<;u|xT~Y>tOR!_XS|#I2f?O(u#dEckT;%+ ztJ?8%r|>v^7U<2v5<}3p-pHgN^osv5Et8jMW=gx_jLGHZPe7vGFAy)Z;KnUp{BSU7 z=2d5E&78ka4gYnF?9(JArFADcDfDkkZ1exR{is0O=f255A5X0VCy!KNgR?N0N+&+)H_vD z=~G0jZv6xf8I6@6Db;%Ed}6L&RpgQ;fwI%zV^*h@jDMG#N=oDeofkw(E%1U}?G!Pk z)P0Q01B@aFq4^ioI|aQ=x@F}=Z}1_w2{Y0UX8xT_YMt!w%7eSIH)wX+%df%1W87_M z56q58)y0ZQxMR?n%F+9`XI8Y{Kg^4+Uu`J8l@RsB^&}+yo_UfMk?=0TFPvnUZ$wJ##>&iPhTbbns(cwy!MNOZurbo%{1Pkjoqh9wsi9cCwF76pvuDG+Z)V+4&pwk<2m2xe?Law!hEY@#%`a3TOYwf6J*2SAiop?KybJMF zPXD2kP@UGEbvam0h~Bc=7th0u0F~i(vc`#Q zlFNAWWf~1yHbA*^eBP%vr17VKbA9t5?*femZ;h6SRgl2h;S0En}T5ur*&yb z0M1a*PWYx_GMU`;@E2+T;V^Q5`2nx3mON@u$e}T&+ao_s3Bc z`Z&stO0RKGdQBUJ586f9bO_o%Co`%EZaH#|Y@w zOzQjK+PDFJxJ@08jzVp`^E>Ml#a4K%^Yc+CR(9PFk*3hnsQaa$Ki{d$D*dEtydcUF zyjG9|E>+VKD(D(&beXGw^-uoIohnx(5tI6gg+UK&hml6^Jp>4FDX=gQ^vTtAD+t%& z+x`AGo*WvjwSn^$j?J`rvu@0p&mIeIyGn>3AWuxed4~Uvzl1f`?MSN~S-)$#YH_o~ zr)1!Wm6%yg11O~4$TE=XGiNM3oNntBZr4nb$f658pzvMa^z`z5ExZ*zbsRyHp+*pa z3*q|Qk$C`qJoylh;D%y=(PM~)v}U7}u|T7hJUOmimAE|OmeXD;ltc=Bv|G;8&00SoG% z_w?x3SC1tVR-{yk-&NcYLJyUHW8E9@BEK!kZ0UnyqQ;gpqljC4se zP>HST^GTr48)7M#P~hs*EMH12J3znWG!m(BZ4qEW03u$asy6MU{|Fh&;ve6+=UjhA zPaaqjL5JTMs-;z9B*^8)GGiHl&9GEVdR&BNI(cQsg;D|S`uL|y348Ad66*|`Y$yC< zw5*;vykgvV^32{E=wh*!O;VsuNaP)0PTbF1!I8Qy`XqS)us>+=rl;Z^@eJ?pF~APG zhvoRSYMz+)#TxM)4Oc3wkQf`!?lVNn`_Qm}JU=-99WI=-di;(p-_Bm&ebMzTm?`4v zxo^`8UFSb`&6Rqn5s#vB{=X9p6csBO6}cI%M2Gagm2}fxsaoDe+0p9Sc{RJcLRduI z$*84W;sBssL}6s&Jc)iY36KWfI>t@xKY!!a~QutE~Tqf^dS7t)PSA zv0DDzOU$|cvjv>Bo6t~Ez^hHN0&V_d^Xnsrg1QAHVDaCuyVEhr?je=i7i*NMA{Wm3 zyq>ohot?Ku$#Oseprkv1xGLdN_vxh`tH3OS)TvR_Q1nzb9f0X?wR6*eDj&NhS<+O0w=mKd9o% zlHEDi`PBU#&2==oQUm;j>+{>sp@q6#E8WW*20bDZu@2=o5>>i1LFTdm^IRqqItn^% z{X`c%`u6qC7>#QK&Lvd*y;Y=R+m-pG+=O13U-~a{HyT+$JG_tFW20Bl^uRsp**y-u z$5Sj&wlw}zUY`wF!8{LRgRpC-e?#SxI0H_OQUMa@Rk^n(5RV;eey>-=INm$mt5@w< zU)*#Y;rWiOy`62N$Q3$M8{5Gqd@J8#{2+;INo=&$^4!bZvltoaPeO)|nR$B0pDSH= z#eb{me7e|7W&`WibE2M*swZ69N-(h9PaLuX!xlGA1IQrljbENb_-XD2n8>3vKEq1( zD}3L8GnQpA7 zME@v9%qcCXStm)H#c((^vMYei1ok1#d>JYJIg=3UbWLO zd_&4I%P;Z0Yu%lxl6Bd8Dl7iAQ(6CtYbUTEX`_4MTR-W1#?5(RJ0>QMCoGg1_AQ|` zIg2N&Q73|-fr{%Qf zR=sqH(L@GeFvK{AQVm5rQue(R`+jP7Y+eaEbn3~GX_q7Dt7J*>3vORR*rI#2d=zDR zQFGKksfNPd^B}uu5EwqgE)jf9*o>xr+iBg>`=|N#^K@V8v5FlRiUkv&uX6X~9s@Jb zFwHRE#D6u$6#k%f1Nw(0S_t5cJrQ58SZnW$xYmSYK$q6cd-ar@&bAqHh@R_Kq!x6@ z^QLRW5pj3dDH41lhg@?1$+X%!kni>x$*0ETyZn15$3O1!{wn|ESH@e}-#D+3Sk504 zb++>P(piqpD$P?_DgR(v+xh-e{I+ORyD zR0?R}Om;*^^ja0OTp&I<$s3?!yMWG9Kt;H$z;yyfG9?0UhX^x$t%0y;5g9lr#bZ(K zi-08`zpFja{8f|UnRNYm*sP8KL6~^C%o=a^!w~XtEdRUneI))x6TjrTco&V>>CYk8 z)%8?{f*P*Uf}*83w>h)^pc9!iqIsdacjQR{fU|$CE7GHj&!5RNVHFbJ@F&1IcRE`< z@&2*JMLTPmN#f8s<4=_lw~e+Q=@+Z2zww)Iidl(1p|ZM#^2lf8N{+IJOb1K}P|L<-a6F zxa=eMQaW4QytuIhsE*M;1C{UAZ5JX4p!d+^KWO8T$UX9vs`fOxGrFF{s#^>AXv9v6 zffETF>?y@k3FaxG7ke^tgyEK%G|N-C1tJq2Y=$rV6Py)KxD)oT5F;VO3Ll8?l16Dt ziY$+9lIE`o$LGPtSW3wnR^<;bMY}R&LZ5#65W7GVC$k1;_|7H(3-W`0uRz_-#homi z{1NB~VnE7ToF6n_K&7+{HwP1J2ELL_ScwnS=vJ_RX}Wj4;uYR2Mn}^0k!{#Xh;vM7 zL!0b0Ln*3xfv&PAm;7Go-RSn)2}U5scox5Mz&K-|ZAa~p>c16Dm5DCl7#%xgZMl>u zqDN&qRm8OR0C%Fbh-GIy2M+u+a5wOTshi%VxMX>2PAP!c>Xqe3hGGILfeI~B(jY0Y zIOorUu}*Wh&^`CgqN!Q`=VD%h*I=U^bOJYaW+Q zrbpmLXZ*nw&d!ONIT+Y5$}wkFFw;Q7BkEyQ#q8at^k5XRBB)JI{?4RAd2Hjx1Fgu% zQRb44dl&rr__b%dT^T@RvfSDA>6HyE$ch#M0^+fz_?2ML|BU0suc#jv*BUm9a$5vy z@*vUOUD+z9O8%kP-t_$z1j}ufy%9Y4UQNn&Ok&u5E>IGj!=N>Pp3CCb(o0=KF8_oX zkmC}t$fuD{g;(91T$&4fekB2wd}3QAkv`W`_lR`UGk6(I7$MTs@8)f(Ln!w1e=X;> zi_mO`7~W8^qE7)H`cW_VZTBxgj2D&8N7Qzm9+Qis#M#gYV)HKE#>4<1U6(tG;ny!2 zhLOXd#TkRgfvT@ACv{z}8mx2}5>=4z@2?!|dX`dBrm4@C%!C)bz{i&5KZ)mij!7{X02^k}p~Cg9 zTnM|dg6exb-R;phgwGS%=ZA|&!jdu1+jLNwKK2NssNgN0c$Uj zj^^bsAB&Kf)wQS9(I-o-Esn1jngR>Ia(YCbT?xK^L1W zeJ^h&$dgv9L~JEAPZL9YpOQ?Fg(BjE3A%3gjCpw(NAWsLIlIu*=nFPSV$XTL?9gB=4%dXT{!x%C7>i& zI0L6EgGJy*NUjW6RlzwIL=~Rkgy(~((fRR}d|^Ck&*Euw7f~lV<>o*G9zDEyL|fdE)h@yS zY(9kNv+(P~(7@1Ec~QXgmxbZZ6vup}wLDXDCyAlG9Dgl#)q+t_!Ly=~sjm{>dO6cw zUO-22ioWpc?H<~X<>(&&o2Q7-8&5y#v`Kl%*r->5aO2Wh7hU#eGKCyaQtu%_2T+7F zXw>kN#50}{-0(oO(IkJvK_;Y&5rS~smpVP_3pNQWS=~nlS=GFBb`EV!f3ZoEfPOLU zk2iMuvgsh;Af&5 zQ;5u;ka8sG?S+j5KFv@{uqk)l&x6@RCW9`d>!QVhUj~;sg1j)ChF{@OMy|CDCUcH- z&Dsr~dsW*NH?rsaY!??I5KdUh$M=3XCh!VbjzOy%mo!+~@aeww5HD@{ zi7!8O1h{|Cp9wxk**u7fIjd;(#3-+G~9LVSs%g>$4s-*^abw}(nLeal40SmtTMr~ zmv=0yJ;A}UBEt#>&C(WOE!*e{(2NdG_`cp+Wkh|($%mG4@_Fqg0#2rv0|HEH<( z5|@5*D|8X9n;-g7XwKYdBN(9{E;I%FCF=bLOt+@2%|O?&8a%Gl$QdVsw>bAM`pe%E z4s<`G_3M0db$syVpjTU`HAP%aytR$Ms^aveT&*6klVyZBiWFvcvJ@H8Y%>bW;ERC% z5sc8b6KdL8&f4V>|bl-Pi)I%=W`cV?oBVR zXHR1xKKXX%fxl_Tyc%OBPfS|Jk@3x=yv_la*0fKPolSpvOkxp9)lcjn+KW_(ed{8yC`JcQC47%>HoSK;qiA5}xX)lka%8dK?{{ zdpvAxv`@Vf&pz*qe`%#}^C}(>c=)NrQ^7vv99R0#Z~QT>Tu+|>KWti#S#AB!p*V`( zRYdM$r;_$d#H&d4tg9pw&*_Imxa7z;Y8ZESOxxRzQ9Vg89+7~Fd>iNcE^gbmRX;wsiGK> z?D8uxa9V2A?yAeap9OOBuR7yDfcEyoZT^&x+Vr(m;h`Q7)mUXKK`#8FcEH|CqdGCk zLV|gjf@5K*jAPo`nUFJjvBQiGby-~g;$68(ba zKu?!atI)>dipd^L=tU$+p!+MsoQpFO=%md~u;r!x$uJVrejJRWJm}kmqw9?csIW*6 zDEq?+mryk)@Vv=XCk4y~{J|vp7#YGd?k z+fUpVH|Vq9Z<>I30t%cP9M+|udK@YjH*C)eaquQ7VQ36-#&vvs%k_h> z<3F`%^1)%pt+Md;9lH~qHUdnBV&3?^rtz)6&7+47btidhG(JA!y53^1PZyW1$IdZ^ zzLbqOZlDK^4DeNU19XS17CbNbFLIv6qut7l_&5lSBTXJFkY24-QcnBLrl;V}whu3; zV)$W!Ra|!zYWzCdsjrmf9EH7ydx$}oR_IKN8LD2Q|S}LYZ%W&5N%~=Rqi+^2DA_$fH&D<7g`=7 z9%s;rgG(O1Vtz-B;oobN)~O$<9PRs7!QMLdMNh(Qmmk5pAH^8B&p6%x_|F%dWWV!> z=kHECs?LTbA5VUX)$w`oRuwez?HuUwf%f+ed|6v0%wVT4;%Gek8LOY5L85yXknT{j zrr9e1B*=QGLc;|B-cQD(viy*IMEKLSoZuJ`&3Dp}~m zXbr2nGeB6qi!l>O4~ZTtKMG}2;<7}&tJtLAMR6Gd2SQ?ojgTPA9+J~Op#b`yript5 z9nZr*@|r_Oq?2xdi;i_&WmWrV+f|WQ*6EUR^`!~A6W|>NRVAS&n$yX}nbXaDp(tC? zIzw;Ovmit{7LXK&Tp=2IiN4wrTf4p%UL`4D7|x%mOw28wPLX+C+lVmTy4BzLdzco` z6&~*H^dmnGv9hfF=_i{N5Z>2GFk&=USDIg9^Xw8JAA!fu)}I=A7b|PNiZWX(ZTT3~ zqtS$w`0S@$#fZAI({xSVN5IijZrg}gJw*Z5dsTh0+#f5AiODc9-$DhdIWOmY{cFF* zgqv?wgj7)t8jMcVpq9!c7O4o>J zpy-7Atl22qz#TO+CFwSu!FQBzT)MB&BF!X+Sa>HYU;DjUc?doKqvYp`2?5~793f{a zm8KUpcy%ODUGYr23}5pq{knfoy%-_NMOYN>7;+0sH-Zp!2_Q`rl<+E&W${Li*Ogn@ z%oF&X8lmGBH%NbMDZR5qq?aV@#3`;_QgqxyaC`}}Y;%U#HT@vTK7vPP35|~ZQbg^M z{c~C$gOWo7nsk1A5UUCCrQuJ?2)<`9m-vH$-zR(RzZDf#bXXKkJB=AICUhS!9lCM+ zd+3g!&zZtkTrPRw2_qD|!Kz>cbwgZ&e8)T_h>m(?kwNo!_ydwl)GEJcN&m7+fz5P39p0ZN)J45PGv0_|q;5J|(s)A#{x$b8&gatwh0wnGGegn#D zg2Cj55p_>Q&Ok)=_8Lg45JIYHIA7op@4*MKX@Y6AG{3{n-1N!dAD1NrC4Do);sWg ziIZszLcNiW2&xZ$=0+#^SpPKo8)Ei}Zp7N4ICAmBXMW_tnTcU3R`TzOkK5ZwzqbcZ zmx+;-eXkHprf%Fgp?5OCoCwM==z<|i$O@@Y;@TzS@Yk8!Gk~&ukdH@;^F_hbOH$+aO=D57;CyRQg~Gw z$Ash|Iws{bL;YLk@+VpB?zUyDi9_(`V(4ETmYdZ^a!8j8=+j$8#xqpub)GRBIw?N39k>U?$%mMd10B;TnZbbdur3 z6ya}-q45y8(&D+SeIMz37A*hkIoP}Z>tkB5m6kCw-zguy<+{ou8h)n&1DhY}T?&YG z_uTAIOVSV_;OOd~-9$4lyj|vsE6~nw9}l zawF@~b9j%0x+Y?a@8i#(NJiS_^EQu1_zBwVka8XppW)b0sib1-5=rBc#wY}$4?s!o z`+eR6in(%qg>Z{)JAi^UoD{iJj#jaLdLsD!!qp^+*G`yr&@PD?ZA>-MGkK_4Ir?jL z$`qpYBNV^G$GrgO(wb`$xt=P&;I!QCE>cAf6$3CNOhN|Tf{B=Aa_s6C2OpXdoosE2 zWOYwy#>7$RLX$~M5uO;#J?W>0XnMvLy1-(F7J}x@czu$8@iIc-elqB<$PYE`El+MZ}=c2TK8aDx6`c$zm`hm0g06(elJ$N72HyCsW+ zE(u*z2#Z$$+1%c8k&0~ahWj13XbLIZV|;+4GFdHlw-x+u-}AegTE9b_FVE;zdbF_! z)RX3AJ+8&Ksc%h0ZVzLgR1m7p*RTX01qmvDGyZwL7>7Yx)Ed#A(4igNbvx?FR=Wt& zlQ!Sv+&b^s5${#0U0J9}B??afIJ<`LnODnlWh&1`0&`A5N^}Sk<<}&L{Y@jfK6`3c zxZB&oUw?`Hq0NH%2q)zEo2At%&l4Oun)#U#L!n=Ez_Av-Mp5 zGq%-MC;%19v60}ft2m#x4w#Wc99#|dCi28b(WaPu-7^l={`h4K|KIH zG{K!b(Su~R73yX^nr@_~4Yr>;S8b@vqLEu6KOz<>%d!?4dON@iuO#9MUBl2@ z&mAqDZjnE<`n&o|QD|jJ|BQUXJx!iix%)#*_la539C7iSklP7A5CK91VI~;2AVOxA zu_AO6iC_Kta?Yo@$C$vMowyx(SpI|FYw-51L79-b&%ZE!dt(2uLH=jo4B@J!bU02J zBD&$fg7)grSGGZ*TT&Ue0yoKmF2QR_?ytrXgT4#2nLK`HTXNcBZm)@xj$Y@Og%ZNX zcVO5gV-o?6B5V1fF)s(f`NjYDYT>*FMb$4T#Zp}}8swh? z$axAPV|IqsZHUNUqZ5gvql->){wJpO1ls)Z>s@%5)A3#RXQTeaAz8u&z=Dt`&eKtu z!x*+5CKM6XzBJ%yXv(Y)qbHt2f^m;=8XhpVm-2i^6i${fY3K&$bb|jURMQGsIjs4uMEyo_@R(w}Y9DMTaw!|j;8y8iRZryFRB@IP36 z%L|gI*PjL-?)r_o60+K)r=|q=&&l|hJ`@etR*966m|{$YSOAr}9v|*_g)zM|;WYai zNE||5IyaI}PiosW{RnKLI(!ZJAdg45u^sP&gi)NcK^r`(g>r0@M4*S6I;xvMB-W~j zE}+g|Ehrh}qjQt)i~s;5`py>|IX=E0RCL6P_=GqEuJ&u_S+*wxb)L}40zMjK?|+UI zc5Ak!To~^G2Dz}-*aOnxDleSH}-o1@F!Vj9ls$J%$&4W)u&_sb6(#9KzdgoknFJN_;j&#fWWDNSS{*SSwN+dLUfI zPIPr15$-sgS`;09f8bdyp0?fp_C@^SyXE_t)k+_x3~;Q$$0iTajta+%4ZrpwgO~qG zp46a^(kMa^SJ0CHzj}w|!l_V4uzNueaxtXJn$FDipqRL@%kqbDs}uX~wSqET-woCs z9*~FGz8CFx=0M$D`HB@W92aj=z(RW$(BSoUmz7^Fvo*%VWly0@ht-CJCYi{n-J^wu z5*EYLFe#nA5!rV?Z;U-`X1IdhxSj@@CTbXwf~Yr|=KG;7?wS0672AN{(&#*nKOIu$ zg;!LGE@%s>7Jo%+Fa2;`q8o)v`fKVH8h?3N z%dNjwC)9LG~@AU)`Le$iCMOt*K91mFc z7m{CSz-F?fmJlW;>t8r(TdnP?_C>rKR*yw+=j+;Y%LOupsTSiyG|vfPKU6d7u^$wt z^Ap}iZ}MH1h|aE3F$?sMl%x#r%S6|QBzo{$QRjU~DSKpY>u&u$hd(2F^cLR}YhB_< zgF8a4%M>*!c*DO=8D-LlGSBB&=xGRa?_n)$>U>nW%NY>UoC`L~;#hqPk%mqthah{d z=RS0R4kdbt4n7Ltm+^MYo`M@oycdSTeOY7vUN^0l9C{xfX|wK^4Mac5#|0kWMcyk@ zrZdlYGUb-I7(!N5P^jYI=Qq3Y6s%$i1=seKU|T=cLKqFLHuA+BZxqk+U{o;pqg3u` z#_dY`Rc8YD8`GQ&2|i21ILFao6>R`rvRc36X*2S<6}Q;TU5|i%{6f@bRuiLsEj53g z)`U7*Sw;i^BnsDY382v*HR-0=REe?pjl$dUWqTKv^*T1-xW1#5>MaY-|2p0812ZzQ z-qlsWN1vr3djWl7`E!-6Xh+KosJ4*NL>W&{L*9Bi@jT&9N-wbyMMq(mPcv)H^N8~jF3o@@ zPzL#P*i{*R0eV(H3@RVFm}3?467-&|I=>9~x<|EGDAy@1v`2I!v%=xrgkbg&El1&6 zSV)5EuF)t4{84Z?0_FJDBzyQ59)b(}@}7|A#`s8Wmg5w~WtEAh2 zovvc_+0N3R+k?4t1X38}yPYp3M_|&>*6*KzwwtH9z!QaoeF-M}&VxfnG8|zX_2V{Q za)3~Ddk+0IR~*RlLjOk?!yE-TyIixNHqu_ak*fd_b>75aTGf~K?zuuiSzAqjrOJn}CHwB_;_Rda9NFMg!p4ar2 zF*q;y8Z;5er@lrTS|I?qzNr1;`Ixb@yE@_{h9gmO===Ngm*B=08^j6tv!fnKE4DBP zuz4Zx0r=a0pw}(u8Ob8F7v5GQEOCWq=oNb^-PKUn@ofdP_J1fb^YV|%F^5q5h?*UJ zNcX3IQrxtR6KYRE+l>a#5mm(k{30E3&D3{gI?%Mc=XveK~b6Lm({;}db<7vd4%29+jbr24)y`Jw|{$w5EtxziweaA z|3jDDgO1kYwc~>CPot~&U(0o_s{X7kj6j8#(@TI8SOuP8#~>1@h5s?~|M8P*r;4AZgz8Cnw@1=WmqWLx$`HpBq>Z})KN$Ehkg;NU%)B3XUz&TZVP2KEbd zr`ez?9Z>o$&L^UCx_Gz(p0(x)+C)p7eL>R9w>B}eYZ_Z88>6dm3U18}>%_mo$TbTbt*;@xy z{Y8DFfCo7Ap&Je*NH@}lMo_|_Lj(lrZs}4&8l^)K>F$t{?rxEk4k@X-`MvYZd;hre z$esEA~)L@@3AjdY}~3eK~A286#in6Qf9+Xx05}Drqt7J+@nD!4vQANOH2=-vwS#O2=pG z=iqv37mS7q8gBz!H11gYt9Cqt3kBPZ9?E>~w|m%|8Kmhst#J}xy}2npA7NB3-&E@) z<))-5$DKk%#v7}KkJ{$y#iqnWc8Vo3GqF9K{mN%nrhR(Xd&;wnj^FFFPg`IN4k$?w zg2FIW33Ye2^}>aML}_T9(VUo6Zu!-V3(Z{IhxLTW2Jge-PhV?}3ny3v?@^#xf7~Lc zp{nt2#D|v{lz1Xnm3LTIwb8eqwfyvhsD7q>G4+^CBpqGl zV{x0SbPccYd}F6SGEbF2uDaCEFJV+(+;h6Yd9Wb7eD3th=i+QP(`%bkRn?VWmtTC) zH~T!g&wFkMz#GLva^1@?U+5S*^V`f+DG$++M|K?11P_HV=jsg^WNxlJbR!c|r_)<6 zp7uF*=^dR~~$r=4all<<2-8 z@V;c2!%Q&#rMq04k@0!bAIYslDs*^r{RG=}BB_+X;)^dVAGH8SI z#&XTDm&>>%^LmkV3YVUyV~)O7OFK~NXuKD&3KOi%^GK93q0q5{z+tmj_}-P7TtfxM zf4MIwjuM#4Z1$SyKi#}tyrP($k}j_uP+J%=8^Y*`kSeJE+kSQYojY!c`tO1(NjG0Q z`{>>j*~2;q8NZO=)NhChtGQk5?%=Ac z@vOfY<7|hc{%o=!@D{T?wG~mW*~1(`_ujbqI=Q>KpR(*c5o~*`s8|%&X(>0T`CQkMMwR)W(y9y>PVP zqEI?pV;d8?h21Iau%g)AWhbdMT>GZojQt1`0>f_zMrJWk6@*^H_`~g%j%!XP{M?;} z7)DUUjw6;Rg6%e#CED^+Nkoxt|GYOxXU}9>@bsZi7taK9<)Ug7rj6WrLebK$_kUF2 zq_%l4@%6^DeRV|4QeTr%Auo|@ka}Mpq{!xS_!GE*moJ@xrRl!f`R7I46AE z2MT1fGIgL0%DOp+I!1?yMs8N8wu5>35ipG_#yIB}>R4l%JXjQ;>nkQHub z#bKwWfrl!AXW?c)K|<-zF8n0b`|t0QqA=&WYJ#Ujem0aMpGfkC=j6TQ6<`p0V`zl6 zSi%!+#`I3s=#{n6q?!ggI*URzYHfM11+MxA!c9K4`TY{NI#)|66`fhQ$W0!EPS@+l zhZ}S*E4oD5*p$>8my3H(+Ons;uMvtr5M;V)xD|zqb-8R&iB1_FTDz)9%`%Mv8JClD zy6>H1m#QsG1ZR3_YI1|%*y+~Lam%nleQ|>*pUcXA1ASFIiTZDc83ntnAuR9qBzBQY z%JFb6!)NM)K{AHtSnlsTy=2uBu@W`Kj`5!IzR3J-;@c}#sAfHI9s80bo(>ESbJ zPLBLKkYs-PdGSE3uo1M1iV~ zjL!;O)?d&U&&E-CwjQev=<6-RI;;=K)kQ&paaW~k^Iv+7fbwC`msKnz!ziXRj33(h zUjnqsHs%ZZiz$UjGeI=NPaB3^5eqYf-V1@{)zw&n5p%t+d46Q7xt{fg9`9ey-ft_D zc~KWpZDXZ+Alw=&g{*@dFv%;XX-&!f5;29R;aRUX>ITiWnJwANf;L<4#g4vXMxrBl z#TO{QB3db@zfIlBC!+ZiXCA!kEuxv8G(KWAm>aCL8vKFd(4DpSS-sYYntrm9`v-Z& z0eBE3T47cwdfBY<+D$yWje?8$zLi^e{gY4qEi0hHjFXW!fD&TtlqLzVZHtzr$gGOv zxeN`8(N*2U$I5ZC+N0=XD%^|{Wu}h!+&btIA647Td$p0ZCFA)c59BYN|H+Rv{gQ|z zU-wHSSaaq2f;7+u6<<#hR&K{3)sE>TE+yuV{u?x}vp{!j*ZTxa?DfG^L;zWo6PhlE z_`c$#G*+~)7l7_l<}PtT&DCzNHyDZtIFU}d9lXiLSh?uJBEydz4(o(kx?y^v-_Nv> zm6F;ub)!e;4IP9ke2^k@BbnO6I9q^!7H?8E*W?f0V@Z2-a&+$j?Y1AM);a)P86AAv zP<{5+5T6b;Z7ekO)z{6?oF{%&Ef+DrhgyZdJ?&^N7aTfy#~y;jo26<={uyEvJ3G9M zXMh1s7Fv?rS@}4DnuRa&zUk&|ac82o(8!?j^G1d^H|!7GqU>COuiPFrxwp|34X>HL zs1cJ4a9uObYumzSLdm!{DcVEje^2CE;Q?1;Z2YNQ9wnMO-Blp7*6vFg z+JDx@j~1R}D!<=emD@%W6~C$XzW%CakfO;P4F7XWM~o3);!ul>jms7!V-6CGntKi2 zUl=psd}!4yjY1pv+jGE@b1?4$n#D$@TjNp=_bRaNnw8LV?xJJN7SBc9LX^Bk;1JYq z&Ao1F)ui7n-3oW2peycVk;1S2u*6}3eF}GLZ18?7Dx6j8iBzTCc^$qkUf@`6_04AW zm-yo#wFrfE=0m(!Ls3H53~97ger-?YDSMCJ&UCflyk8;K zu;aAnr+sOgsojDXX~v0;Gf!Ur*%%(U5K8#zinMULG~_EOdPS13tWP}~X#B1~H026i zx6=*L#z20`VvKdN>ht4xw8ACl0q)s5&vmuc7UAm|7ZmlLy}C9Dw3HFWg%{EBxEiPR zh~SI%p`+xj76q~?i$%85^k7yUR5b2JyHzRs!K-k=rMG0ki=|8Q$m5jzhJBD<2fL&o z@VSb#Wl=q~;JDCzkZb4bCfS1Y2wE&I9<8+PHQ>#?zYw(k>kBLIq}nmAsQS3Q%KA{i zgR4!}OpgP&Eys}xuu7{ z+oajqk7dHl>ZQu?J@LRQM3!U8+a-Z}sl8o0I>iR!92Mk7@F|yXHSEMize|k*5fQ;x z+GeRtxs-$NM_%tQ$Zc4A8SXcQHJdlF)z;S5(ReL#b91P_vdujz_c*q=@{-9zr&3mT z^QAWtMQd(>aV!BuLd@~LSK38RrrKY@1H)D0lapt3&nR!|tF=7S~o zE7>+x9<2Cnd7Pd`lDb^{&_0YIz>_9#KOkn^JDTxvWm#g9+04f+L>P@qkH`1*;=9nG z9N|eqaIe-TuR*Y-x9+8Kvi_V^%+{)@D_h*Ohv+NxSGf?82*hkwnc}yt9qzOKp9d9B z=NY1oYTj*rFZv>qo-OcJHAA7{f3N_W)fXpo@{X{ittvaB@ezm^CI+-hvjm!m2AR}ct-LF!uNkz=xLQ6B+#t;#cNA5|m<>fE*!fN&CUm2Asx+6q z^o815+;7GrF?+FECInZcjA7)7`!m;>EeDYU2)(=6ktNOo2k2ZmrX?Q6+QT3ouh-=h zQ0%w$4O+b3;RW?G6J7URM6Yu2{c^27Ica*kG#|$lvTLV{5@i&!p&F!eHJxR>pr7HR zpw1)vLLFxGc?wiJ>MD8#Cu--dvV;cRtBy$fzV>xDdgtb}F5mx;$nlrW(PB)AH{axt zvS=LMqVcYvR5{`-chI7!J}NVL%N_iR%C_akLHVLZ$+r7duM z^(ArBhqe1isycH=BoFrt^qGMSJ^wS@;Fo@aLYRA0y4pvSpI_x)tV2hraTEAKESop+&oHKIg81_Fj$jxvT#dC_SX5M8<~6 zz89DRDV?86OGLW;b4d`zj9d)HEyu6$30ZZ*N3B;DKfqxbhi?I5KjFF5M>|?u+E_oI6%p)%|wp_WmpIsF?VMTYn((A-*OG z)G%^r`%K2*b>K>s^^b7!BCm94x<&J!*)0d~tG*C=CZuRof9!_-eKTp=5M8`1HpKVc z`FbIlv0$u-EmTI|$buj&QZzjS3euPD=i^FxMc)pU@HE|@HySm;s$8c$mG#U;@`ijW zS|Msyla9v4qk_gSOkGb|lTfFA>k z{t72l2I`t@H(6dPTOQFU{xzGy7kRY{`3VCu0o+d(KLcrp4$Iao5YrE4jor%A!`XE9 zD)nAla{2geaNz=d0jiqP-AI3Xl}?dHKw9r-5coW&acW*!U+s<@TyPYeZFKo!MUgAJ z@ih*r&v5K`DvYin&3t`*VVi0wq?)R*qWuXx4b#l|M+rffMJy-S#W(E+)5LL+S8il& z40Id_zT+|NDK6RRZc=|F+I7+U0C~lPq>h%>dv>%YSLt(EZ8@ppnJHCKb@OUovYHv0 z0Q&f47bVteaZ2jnJ^!z-l@#J-%Jj2YpMLWSW11nfV$#tW?blg3-CI zaYZ)Z!T?CK(mYTs|6>SUHQ(Wlk!a|^g5ID9(=lG!;F%wF;Y*{AV; zH{7zwZ^(2_pX{^6?^T4qf&L^n#u@?-gT+5nr#>f=3)!S(^a)BNT2Z9jza?=*0m-O3 zd14^0gB2pQeOjse#}H1SNxi5xlZWhCu@B8KoONlE#(aqVR05P5I-!C|6bWn`RG6ht z9wv0vwM|P-(t%}=2^48i`jDwt^v+fTamzfk!T@3)`(wQaM1JD+Os7rBCY^Dr`j!cA zMx@KqQ{ca+$MsfI9&w_a=P8h99f8MH`h1PTLu+o59C| z+6U71)(Z`_G*(0wjphkL=0gaWe`-P!cxFih)^g1?3%VK4 z3Ubqt&kB9$NCt%wf$}99Q6}Naf(WUhSn9t7bs)rt!MLT`nH~Ec@+`6DM~b9*QK*a= z(ht|?_z*5C7p)!i%f1>qfhzf}dcK_8k3{a_ZG7T0+Z>wJt+t5=z+i|Z0n7W;qAo-Y zKNC%qC(w;q)*3yIV^k%b@vfnjPilI3X2=W>6=RH1d6P~SBd_xxWjMjQrW2TtG;9)t z^Bqigeo&8;$fLe_o}EQCl~J~>S?TrL_j@+GESxx9D3k<{kS^W%OVc;h~$V8NcBlw^3#wyB5AA;iak(6HAVtI-D+XfO3a&0z)yJE;RW05?H2$Ac6 z{$%$6ZIk_Qm$`kEi6oE82rJKJ6c)Bw%zMB^G8rl9g*8x9EwLTlnz_l>%k%Rm0KSe1 z#-U82_s${nd)}Dy44W@m`9Hp=A@U?_huq%+@HMnQfx+v16Pagvn_TuN|6Sua<|x)d zi$Bhb0B{reLTE_!ppS1*cDy*XtM1t~$`bN_XPRsX}1R1--FjS*h9+8$vUlx}Ib zHf{LQz8^G#A15>h-=-@Mas2C(-v$|;*gjInIwI=nXR-L=-$YV_$#N}u5cK0(W7s+laL=jE{8R^lCDlp)3Z#3$Qa533bD5#6+Jl3IYKq%!cm~ zZo+T7W6?iAd&4X&Q>;nvl$M{u_fHaU{{Y5?LX@#A7jqniAi!75kKvg9@F6}theNM$ z=6qf=#&I!%zTUO|R)+4f+`%o=Xo$4YF&OFmDhl#q1iTvJ-i-B)*QGvVR^AN=kUdcg zbn#od3!#WU2n(V!Jc>99aY&$8tfOC+PR49X!S5pA&^bw~yxSHacX(QJ*PWHZGLWdT z#BG#O9w6Y29vruCSaIK~oAWD0C2l0AFQ-zK0tZ~<9fSXqEg)Y1>zBm@=+tqTi-bH4 zMR@d!C4GzX<7C&BR%G{KpWu``X>q&eoPgj2{_iH*$ah#|pz9QZ`-k=&F3?a(zx1tdk3~D*{j@=XzDkFI&^^sOvXZU5u zEZ24GVivc5Ex8R!xdjl@MziI#Fy(S)Euueo6aV4)%HBRo3v*_)qHW~LVF)& z1QK?cFUXhCQZl^x6tAyPw;H5iIxD)YXPWYVJds>CI{nDXeQSvA=CEhB|hUi)ESUPAcmRrr4{w_Kc=F z>qWlC?>Y*8b6B;AHa>7~USVusy63Gd3>zN0?yyg(>(o83UABMV)A}3LG==_u;o1NF zuP`L>3=XmsMr491<&3#Zg-7w8>BQY#EQpw91&5G%deIy&9km}1{?`-upVdmhkygF5 zivi}v7g5XE`3N*&hk*iawHmYP6$x%N;2>pXgg?8BE@UkEfP`vpfC>H<86!?~lkuCw zx?QxBa^OI3Y>KB%RH~#3Mh!LTzpV!nMw|+`kjm@s>*}$HDM%0PtbQK0yR7W0uM4|v z;>v<^>UPp~;j?y(yOBu5)cT(=`k<_;ialdp6nPAWLJeU4=Rl-R_7rJLJ~U1F-^BHQ zn8h*=#5QsFXfT`EuLR@y!&SX+V*B3J&_?6>l{xRq?Z4I@Q^$64ci5F*j55#^-Qv|^ zyab`5ewbekS>n;>cJhRLE(E*D=Tr~Z1|06Dn=}3ZvDN=OApGGY-qS zyka}cnrySf=8pCHj@C@A4#)zh{(7>4GKxK&+J7Zqo4R_=tG4q6vKwrSw!>nY!go@r zN_fbaLtobQjuaokiy*89USCWM?6dO+KsiO(@3n=P)6Ts+dJ=KCw}Q~=zNQ|7kcOI^ zG-&X0H4nUS^&XJ#CU>gcHfWlXuGu6rs=SHeX8_n9rVEmKzApk3DlK);1z_{4e+M58IOg zAp|GqugZxR0eK&T_+lBN7*x*XyyGyq=);Py6SQnlJ#$y$9^DcrRxHL!z(RroB#DDq zrMatePu!<4I>k@ibkNXJfAvnPnZ^B9#2XxY80h_eF+?TYk4Il%CRP!i91oti)clq* zl-Qew<8w!+QmyP(qO{jz6=YC4RFUZa6t;c7d}mfSQ|5W>kV@z=*z9vx*>t#FNq9X* zK^mo72F~%HJ^*@QemrNMEs<4q!=TJ;S@Nzzg?%ejKipT;fv1J9$i?ttmx^;WQlxvl zYQC4`(LjblOnQCy9a%rd3_PRbLAnpVkeEcoC*Bz6)6u6zz4k?dYgeUef;gKT=gE#B z(7uaZRI~;gdv&JhjL?~Oc)IGh{>3VUvq*5A!AQoTO+>P@e!B?T5_Yj-yV2(JX9p;CC%F%g64Aymx&_~6!=ATOM$i!`0=+haN%uw?R?RJr5j zei{N|jWJgi5YH)*!t*lE%w48#DFU%as7^TS^ytBm16x}A)_CG z0=&>6;nwvcf1+C#X}x}Gs66miD0F*a=Z=dV0&S6cxgN>^7E^{7Dpi@_(1P$l$d!6| z;9I)#?!EsXu?C|sP+^a}{innTSbjrB@--5-nL$phj5w>?;{)C|1j4IB4ZN8|CXke{%;y-yiQU<2 zUc2Mr9f13+FE&@J339e9AZ~fXrH$7N;uhArs)ftGw`EstH+6U8x!NDVb5e1~-Dr@y zK|?W}b&0u6-RPWSqE@b~_pUMT#sF#8y_*;BM-OaJU01YY2#|$R0+!ipyKax4?vL1X zi!xY4hdwNDiZ-2P+|i4dQvzlq`q$b35QblJ8CG8zzVy6hkmciNjqX+E{Pbz3Gw8B)V9qS|$Lrppxst)}3!Ro2SCG|Jv| zKhBt2>QtH#S7pN&$2j;mOxDK+v3efL-60vH#t2~_&rv){j1$$acPSLRsvszh zOLduf<1nVKz+@__tdtF4Y?uj|RpL6rNRp4E1FyBhjTBjQm9A;QFPng80(AFhWZR?>`>$`ND*K462Wcg4P{Y zdoUWpKtGpyEr8wmY&}~=ajZc5mJyb{Y)Yo*tG2i_6-g~>0>;TN74$gAL?O-zRT5bP zXkFe7o!5`~28tHP>+?0Mwvv=T`gh!^b_k90K=v3&5iWB-di_XMH4#)WZ1G_a$B_7- z%vRebf{FFN-{XP#;KFMkjX!&xA3g(9%67lN|%8~}PdIY@#Z8+?H8 zF}vEn9B@3NXsOK`(g9o5#-!bzbzuJ;?6u`)U~kHDi6A3%}T@ zPG7(@s%+yaHvn5~)1I$X;TL<1Iz{3$ti?Y2%BNHhJ(LEP-%)mhxG1ulL4A52oLSTX zvc8W7ezKWP&V9P0N8Fx>koU@0E}V&CaNC`$ts4%ZcKKLgJ}kZ9xhF35aNE%5>IIl& zNwn|leX%2Sz7nhY=MX#u=wRh8#4qh!Oz7JF{spF+$et|v`1mR8g6(YJI6gvd8ocNn9fBhXca{vU;!y0 zF`9g`am=Y-*9C@s;oIq#Bhl5j5C)LJi)0>#2)UNO?q30e_y_~Z@k}0`cDah%E{IV( zP_3iyv{iw&g2c`7eDs;;p4$rb94GfOsh1Y*8^BqAcwVHZ9d>-%_w=1l{Ns|xN1#+B zZXSjJGKVLR!KS%jyCHbAMM9DQ4)jmy*?7=f@AK6}fD6*RVfm1c?-|G!!FOgVM-2|V zXJvIXmI`QM(yNuL!rg|Z`F&l3LX;|zBIXA7;X%Id zsoKG{Aa;|Mi3`ppd+ZzRYYyo}7^FbgttNOfQ%j2;ln(-^!8ZR@|6ude>x;em22;AV z%tDy<9_S_ZeZ?K91)Z#-o8-NI-JkTOLnk!&B zU?4e43*j$jX_F7sq8fW7eE}H1J-%>X`yBzifjoDqscSHTaV4B@09vyOo_YT8Lh}lB zt8r`6w0-~bn13h9qw6vrLcX?ptZCr>1vIBkTK)3!UmsPK-1!w61S>s2$Gc=Js*%Pj z8<4-a|7x0KlKtVbPASf67%A6;4;Wh)ugujrX0ZJj^l7Dg?6(6!UXA+%TR$84>6K)O zDqWerU z<~L2n6T)T2?AlqZMeTQl|JG0l6m>$DaTNYQ-6^^ds$bCJeYD_@Xgt`zY`9g&%ySvW z_yb=}sY+fkg4VrHX-&U#k}JrPqnCV?;D5oq_WGH8qE=QhQ#y()TsH>Wxf*qen@dN- zXyR3QuiN=Vt6dBq;=tYP0=CTQ96&NxGIPm}_j1-huI}8Ul{JQr!OW9|Y>ULb8vQ}- zrOWguv$SvGd5(^bt6=WnSlaTU*HHvX8QnD)?9yf+p7X{H?z%mt9yb4Qa$iZl$hNp8 zmOP6K8^wx6ow~O(g7RyJ8T89~nr9J%PFptlI>sxG_62|kXIX4hKA0-%+5>E4Mu%DG zJaf8N*#Zd|&vvgNw&gXLHp*+#1@lX+E7hu!quy^&@V3T=_;&I(q%Jw*>^VaBwa_w~ zK&~_?d74YYzR$cGKiwY3s02yumpygXJiuYC5_-UolX(t~aq<^mktDX8`CLtDT4!a@ z7E$X}2(32@sh*uw=1%s5TsM2k9gIeh+fMC25?b%dk!obR_@>E}{bqaNM5W_SiN2^- z3%v(^D5W^`bl6$tJm8*W1pGu(XI}{B?(cdME*a|j!(y_iu-B?(n4K>jhGtu{gvJ3k z_6+qET5!uIi7mx#7cI`*zPaAJapGwo07I#jV(&kJ7=567b-FVieu;byf0+#1ggQqWSK!c$ zlCdMnC^}LJwQB|LN?iRJ6)Tqd-r}f`Ixc`m+s0P!$2#DQXvwKsw$bR%!;=lTCuj9 zY#Y2Q_%K*tmgz5H+K;Qh$80MtSolO)oY+edjjtQR@HhX)JySX0oH*K73Wi4*Uu|T6 z@S2pO3p6e-_GJqy{mxb<(+BR(5f-&B*$Y&CR1|(Ot7t>AV6T1vl0%3#XyXPk28E$f zD^)_mvU!F8M@Fk#5bZ0{2nrz0&X`+%0bnDVK-UZsS+vRL%KfGD@fl@ z!HpxGbbHzsM%wurdocu~6VrG`yohw1N&;(lH4i|5(HEll_ z`%4YUJ+zyJ6_IDH0uKtP*Bm;N)^O54{-{`x^)W#=uVmw31>rs`vtqNHrJ(SX?vemp zkuG4=t;TYrIgfBLeSK|G^5oq%aj8XWHX}@Rp?$ElCq<5$9T%@H#`C^> zwWD_LcasElN&^_0d-|OzsH75iUS`0AigkncTHa zAncTnP@2NM-Cm_nxb-D6v=1nRk)?sX+I5;YQze-srodO-bF;h{C-1GwkyuHR@3Id( zm8U!~YrQiJ|wo9Q-@# zp~Q(z;^U(q&q%EggB?$*M%~o2seT#edNnQV$n=9&q&_AILzih&oj5tI1AGQ~XyD9n zw!#+GB(%*cP%*GQaspn}3eTbS*vJ&A=0+oePx1z;L5l(6_aRv}_Uo!qzBC_D53JIC zxSHAcn$CauuT-d`aaRXUQ@YP}ExUG^EY|RT`!xu4juFTesJ|NU3;SKp!ht7k3}yp* zL?Lhbck(vf+xPm>#C?X8wtfOfD=-VWj-0?+u)C*A_@2@ko!2wnZ{l55!-&sroR_mf zm8!?+w^tx9&Sdsd_5Tz#>JL6xhuqzbHcOx|tHqk%h*By)y@w ztRp1tLWB`E<$mqIbuex0ck0w zH-UoH-ez6|aL9HAw5g5I)Z9Pib0Fz# zyLF2}FD;4ND;bj@BANRb9ez%z{D(xnu# z8@e=6w{5`7M>md1WuE<*sf0RVL*u7w?Jjrpw;BCjY4}N3aTT#4SOk}#M2u|- zDu+o5j~`Rrkhc)hrCn_|91X;RxbhkNxr|#7h?E{D3EA#edptLVInp88>M7j6YZmE! z+wwc3ESg5)pP~4M6wxsJ(zjs{ie*FD;5{gQs`p92GJ(W%9pGvcWEfAAbm9Q_z>(9* z@0d#s*Sx@gi;}KkIOnzx;Zy`u5Y}ebCUI+FcHUa=)VbXs9r@DpGbEn5f*`Z`dg@IZ zD5`v}vSGl={3k!gptA1Sz+K*E{e~bX_Zx8<4c=+pYh{qeZxeCdr8@iwhb1+9|KT`e zWwL%>e#5+&ZgPjz&})h4;+;ad&&p`;R|nxPCYaZGkRPINYZ!w!&6B11SqH zp`g=CFzLQf#%q?Xz_8h%>GDwdHWBn5Db53FWqFXEY0*8P!Q#dy(R<=X7J z+LIE`^H;ZTmn-YrjfOQO4tFV=fy4ctG9i3GqcjD?fr=8J2Topzl$Ir*B5Q6Q+VkV5 zb~tR38*1A%jU`jF#iX3j1u%ZH8r_|Ze0^Sn8v(^uA5hK3EpgZC0Ju{l2K`#@z#$je zuE4(;(gAg~kJn%NP(#bbg~Yg&m~3uaQ#+&#<3?V9t5U zoR4;kS$3)oSM;%Fx#POZ6~>;K?ZC3yMeR?2EQot*i#_NCoAI0rt%7Fb_?>`gH?g?+ zTIaT=PSY8Bg%j`-50{L{XaF7Zdc{X@sk3omvD7MTX#K}+iZIoY;v$F)r^R)pEBfzD zhsZ;;J?Bu%Ht2F!a^x2Ac}6LXShl_O^!nI#uUBh5Wo@(3z7smF+ZtutcNp}qd8g-I z<5$qNQWofwSF)n*D#^53N3%EcVu7}~Hz(Os8Tg@heAGZ8WD%gN!9SS_{1hC8j?6<9 z@baBk1GF4~Xj!F-lFhb=9)tQtqELIS6OawD*wcX~%je23=z^&d*jaE{Z7V!{N)RU3 zfy^Sy(0%+w8NDDgo_3YMl;9~b`(Luu@5v47atdKHw_u{7TX?SbR^sJFDK+s{VbkeR}??ZubysP@eoj!Gpaj@Eo4O=pYr>oE@z zTsjqscnRNqG2Buqm#7v)eq1=1WM6@Nlmo=90)64{!`CU2Zd+{ummv5UU1Wexl&DR5 zUYzW#0-7f#MT$C%+JYZ_QHuXlN{Qz+764nM7IFG+GWNqs6RL_@^*8sjfW;VW<-5&H#kT>5Ca2#|IV5C07ipDd&4Ie>o52NlB>AV~K9}|N zj|9bQ=Td3$J(`B= zoGAK?2(EQu$2DJr!AnaU8+kc7RNyN3>Vt^pd&q)HaCV_ZDzmi0+MrD$Dh-xS#BrzHKmR35uvwF7a_owEple)0Sfss|Z#i;)}F=Tu)3kV|u zuad3kh!`1v1K;nZJF1dR4PJ}7@$AmmgYHhXi`m}U!cqT!ZORvlEr3Fh5i&zOdH&uP z`sX83hnZ@iAkU0#gsNC*-J z7Os7DqD3-o#-|m!U+;4G1-E>fV9P%u0Eh30qCHwA+iklm4R6kvFWvI?A*Q(bi>P|OQAD8pdD)OB!i*2|^Ja*uJg;K8K2C+MtX1~P?DT799MIxb-)&P?x0|YB%R9mc zn{q{&*4^>_m4EGvcz7DtAe)`rxJ1A&aePL)_2HC%`M-0K`dEea2a0 zHGLG}HN6Z`W^pajlR@!=4w1Kn|JH6&vVcT*-A55(Kw=+#RO9k4OWh?2ob4hkTy9qF zXv=Z<&z{fQQk6yUTmSf8M!}+x#1%O|tH?lP;d~e?+hwHhx$3)RHww6Tr%0swX(+~%j3)c6XbzrVkQgR+H z-Mg|TN_8ZL?Z0R&1KX>5t!(@xg%j7*F$^`zQy;2qojAx5TUn1)}Egu76vqzIZEw?H*L^Y{h3{=tS30R z5~s}O=NpYKTb2Jb6L6G!?|_Q%bE=Hf1Vl>7z)yW;)a`oYb_SSd%<2CKOUiE*e7c*F zK*I#WdT&+fK{`0|fJnr`X(XcM-FlU1vH4r-ya5T4!g5KAyV*$CtwcaIAGw25BCCKL z;tG?XcBW46US>H9N$l{0#Fu$@dCNSt zw`=O5ed+TyjU@k&cW@!#u^5o7*NvZAK$z=RPb*}jXG@c#deUgq&+7Ge_~N6aC&@&y zK`>kgHqaxzI`cxS%8D5CeHebvK-*b&ERRhqV|Z!}&yUAR?~U3N{ye&Nf&qT}J;}Hp z*iygwX}zODe`{22ypS?6p3M|a-oav-&;x#_>c!V3vFJUH_#Z3)i-v;{KDV79vx9>^ za&wEYjDG%$);X%Uc#`az5~PkVa=FEp?MX^US_MTiMn)12jxsFlT?%jtL`QoBw|f|M zNvGzC)SKTWDv`MWRs|Bw>sL2hB;Us1S}6mgGO(fl69A1!9=jXMM!_+vILK)f>qV}o zxnXl%F59mpWu#E|Nvo*f>dgA)&FG8?jAdsw(V(6i5s?tXdoMF}b+ff>{hgJSv6PfB zhex!+eqBm`#Ei(%m>sB^KHftAx!?AoZDZIthDtmCc7Gy%b;&;Of}7_iab-#xP?(zE zLNkZ&NhZnG6aL%mf6t(YBa5`GHmu_{w<)WNtzh)J3mZWKkWI{1;bhRv>Gsv6zi49u z0+~5EBoR;gSyj7(truaUB8~$(6;|=zOm@j_$$g?c2 z=X@>)9V?dq6f~G~Ha9dEIzd7hk*uQ@mH46cl1esU+Weht|MGOJy~51U$V?#pQ6MK< zpUfM3y4LCGP=3KzV9eA))e#BTkCDo)l7c*nah!f=;L#9S*N^+jHt$ANg{eWg$I+^^ zA>gd+cc@nG&5R|N>~b@WWe5YmhnSL$2ebu0`;OuXx-o`s>M;k!j@Z5J2WoS>Y^Agf zU`B-LTNacYb1pdV56enAK6kUwO0?=OTC}RM5Xow=ti0==7S?8=<-ukMPRw*`w+qC2 zAEhgX?Y`Ou`&KM>oF#X5dc?Jy`PBfSt3ehkv`E3gZ2PYJnUI`;4V~bk8%oublg0fw z^i|-~z+<6WL_tZ0>0vo$HxrziDOgF4(vCLfpZ@I)nh^LEJnnhyv${Giw9rys+z?@1 zwSXdUBq=LnjK!L7F+4Hy{>?IL$?x_9GI0k+Oe{=wvm=T1m9-hFK{*yyR(T`)QXvJW z?kS!wPRdBNYi>x{(US~Z28t3sR!iBM1}ei!_bj%p_iUh~3zbvQLVWin?TM?yl!%!W0+ zb}UwuHP^l@MwIYzU+=kp1HNfjRddP`13oq>9jw3Hk^%( z4GJ|LK4v$I4suinksFHuzKEJ0g>Es|za596bcp|z`~J1W$px)<_~b&;JKiHT8=Ynf zj><~&_p(DTQU3fOW+>^8vnNy634D6w!YP2aHqxpWPNw017~Q7+RO7uD=3tvr^fR6C zjCZ35${Cc1wR61?bKUrFgX2|26SBWw6Mhb0N#{j1JU`s+8pN6P8WCoBz}n6|>vB9| zs@3d4Y*Y99e#8ED^Iyua|LA%HVW0CEmJ@=TofcR2YMWeJ9R*5Y4pZdYn*5ufC}c=* z0aBw|_tVSOcVCC*M#5+6SjRLp=_w*Iy00;xP%RksMwE`{#Fnh)#PW;>ZoEHyN<_Mv z%_2$La;X={zHRODh_39g^*(>~^bLo3m|)VRWA^VMXY;V-2YV!?`Jq?n>l8}?JX0oYY($uPaWsgRLV|sg!vcWwH{-x{_ zmZIM@$EjQB_&RfQ+Za}u|K?8Se25kjYz5t=EN(_q)-0B*V0x8hABNg*BgY~dKBq|O zGGm2VHSOhy&Blr7YBfRc_Y@{7gUv1J5vDxx;(VZBu2m{GGax^dke!+Pu9rlGQh1-} zEn(>_iZDsgti)^*zO_KHxh^U8D!ds^>b@j)^uFqi{????R3Ha;RvE>(09I6@VdzU2 zqFz$(@Mz-Yi)^utkKiyqJ^n6zLaKi)`rObCMQ7n$mHlhs%@y=T^BIp)Q7yXPrT>)2 zTj0e1BYf~{y3frho^f;dfpjRP62C`&?_JegVU%ta7Up|a*Z|wUw3Ti1@oO^CHz-Q- zk%-KoDb80lJJGAq4+V%^FCvut5+O=T_^h1L{6d|9=7h=kf|)bz8|Iud{`ZkkAE=Rj z+OYBw4gzZ0I@O(9r)#IIjG#h33+ia_8-eA}pIw*x8A7K#O3?p3!tq#rY?`tZyrzDRHjSGXLPK+Pjrs({pcK_S@-no)~Am} zm*$f6L;5I<+6kl6oPRepn<*?63UsTB>c8gi{D1gV@c)Ob_Y7((ZnwTw6hx|m(u*QZ zL8`RSK~QNTND&B~fYM9oL`6Wlh=7!Yrl5%QA`p77p(vfu6Cj}m2qBbbd)|3x?)#h> zhGF;;hU~p_UH|o4YdK!h7@`RJoAJ`pYdO=|l|KK6=)0~p*3047T@5E=J~=Zx-ZBX` zHfP%dJ|J3<8GNF@u3|8k1oWe>Jry~r35|?(dkSGf=s_a`4ccp{MD(Ox!6-Ekx6il{ z*M7IFUkZI&Fdq_XGA3OKDp;(O?Nnb%YSb;U7gmmR6>dTo8uM5lfFtB-CYG{M$VesP zNz8B#$-5+`lz>sED&et*sH$L`O@RaM>#lK+~vh_a!_zJO3BU_0$_e?Ab)H5*EtLsy$4 z10*ssGaDuAdId6VGTmxgf!>f0H#nHT44iDx8GP6PU>9VFB#UcskZn(_vKqPHJcvl;o8~r>n2pSS{93*u{m%@FpAW3tv2u5 z9vwkzNUJ7d_JMdQhWCm+O)1xayTrw5#V@f>86VtxSc(z9GxA387L3~iG;{4zyGPXZ z&U4QUCXZotPi=YGs#EqW?IAOL1(Y)-H_ZiPAW=SLuLfTlPyEg02k}fzMSQ4wkxu2C z8gV+L1_oc}KJQoY9F6Wia8a|UbfWv0GXFWTcW6O_Uj8!h?lw&bXYEB&->C3`0S(6J zdF!)103&O?LjC+;u;@FTR?dQYRjv?WB~3eDk0$$)~+{iTMK}^w4}e>ue`V zWg*LFsGJNc?Z&e93`*sa^gYfXK`Nlvf#-V0)&X~7N|AK?sj1Pb)=XbO{_amvNdO_o zIDb4|iww5QkC)jH&-}3^6(*?vctthuoZNg~=^~+=SmD*h26?0IU8L2z2Vi>pp8!q- zu_+S_D1hazQ<5HBow|_hki92QIW3PhvpMl8J+EH9@*I64*tu~FmXRXU5)T7HVs4c* z9lts`o@nk;qvBdI7{$$RH*7ae*1>=VW5oB&{?-uTNkB29y4L=~Opf!A^6=SZXdA3z zbFzulYY=v$KTU-zvAY@2Uat>;`UN_JYrK{Q=k+&=j^M?`9X$4BVvy&-9%Yr|qyz4Y zjraGtEjG>n&xwVNCnM|gOZ)X1SGi9E6tyu6{vC@1o{y5v0s z-3LzNUd^;H6;L=!8*C_OZoC>XHj3|xq@=B6M91C|eRS7(<_m?No(3I2{(E#YqzfTp zM=*1QEvoFCuFJ!DZEaz~ZnO~%owqDpJzb$8=Rlp%mG-uB!fKK>_eG7if0*1CfUTrl zH?mTDON?LvtENT>ByKMl`0@Ps+5mJ|+oL_2feKhA=s@LIdhP}yV2QfaD7ay>M#pjD zDc>ayMw|B+fKw;#mf$eKjRS5axb&tnIFb*L2-CUPQ`byQ>F6RTr-s`+S%NR zDd(z=ZOkEn+}Z=Vhw1Re2P?o?z6+Ry6$kn0+^6M zl@Y#7bZvATYB9h9XgUx~7|M8f|BwmCs$AID->-xM$29FnVtkGPt?%%6cF@L;^8H5A zx7KO76wxYWVWrqx-MSk{9fa^sJ=$RBoCnaw|$X>#yc)w-G(lwshO zt#FaP$<|71xrL4Xhz(!hAJG~X5j8~mw&$++(U}$lJy5Nu@B8^*8^lM|CB%qYALiHJ zUveibkG?3jDW>8q%EH`4F$2F^eWcQI0?oO!YBfKOrACM_@d#hjzH?D};vw(zzi(0t zZaP;F?b(RGQH)LFa`?U}LkOtQ-~axrzfU%45){>BER5Uc;%1liaTd!A(4971Ty?N-@QQuk}it}+RD#M!oMy~MuaLWFDt zp6o18kXK{MDiPq8-Z;eQKa^HbYa(5c2q@)=5JX_XOl}eEWSJ)z?TL4Vf?xM0%(39$ zwf8haogg|LI2ug&F6;hqFyIZ4fSrZ7B6qu7R614FuK$~rUO+gb$K|cBJ|FaGirdi9 zf3)i;YoVv|wdlA?YMj2oVBtNJev54s*SI@Xe0)4&nAmT9b|daVtC}9*&yZ$q%CM~i zRcey)t{i7);XEh20;*DdT1A(nJB?$oRbrv*I;=o+6|u1rrVz8#>LRe_s51jQKW+4! zgKwfvhZslnLmpHLYXCN%2OgfWWOz1nIIG(t0DAB34GlTc3oW-4p=DwQkp*G)%JqDz z=ZCe;oj}a;NyA=~(Fe>zt@Y^!Fz>!f5fu3+>e`s$0+v;&f|NnK>0?pD{qo>_>_dz% zNAppq7_#;HKIgoBqLN~gLRj|8Ew;D1ueT5Iu2l!m1akZ%azhUMw*K#&55La(yhzgm ztn^aFERqFW+F4k6zZ~k7LOpnkq2U*G{!4XdcNcXz%Y{KQvcy~IRcf)8pPSMKj$OjP z^$ty%6^~bqR7;g_U^Xt{hu4O#8*;> zVfVN&{%?)~hO6@KG5Nb7ji3(Zmzvl0f9(K|lIFq^q8uWYctv#R)aU-Rpc?y}`%DRS@HbGitwW|VQ7u*_J`@oQ@`yBti0 zs(7UTi;q!Z`@368k=%p8puyGz3`Y6Y)qg?s8eTr|`8BKU%Vg2o*-?9wm#>V9I@lW7 zbXy27tkw1$_0Oj+|-z=fj!N9Y?CzG$Q$ z75NK1|E52TM9YoD`JIrX+001mcdEuGat77ysYc%2$RyVFw#nbZMh%*a$347%uiSU> zhKTj=t2CI9$cS5#zG!@)K*dmn{9Eg4`i1lMlR849z5$M^GhRCp*o$ol45} zJoZ*TS`%M=Kh=Iyl5FvtwqGSPs_Wo|P&s4LFO0mE{n8-}ays#TgYjBypSW$GaU8cS z_x(XBa&rowp}8lovdeV$<;~Q%#>aS@rnS6SJ5n$ZLT5J*I&K?IC13_)2fL>Xr;Wq% z{eNVJOQq~LrbnVpXbqdfPLJKWBGGRvA*e&e$@=PWX4yQdsVsRPohA$gADDgl|#9LF4pXiG>19?;4V9{;$^Kn*MsJhJ#haeEV!_? zT&lSYKi&zGv^@G3I{C}Vm;U^4*M)4C-u7K8NX}ni4dLAeAC>`d5Y_^?2HExsKE0xKc<%pLCGrn6##q(y_zK17X z-9L+pyfdNeKH@64`pa!;a#oui#(mk_Wlx$5?yHF0&jRNZZq9#YtLB+}@OYx}&xltL zq1JZ?A`+P9BsUINN`hW+Ai(E(^m z^h~UYi5*6;vfOpVGN*c6$39ud{tNZ+;Nd$nBc5^+jm1X@;kQ1bg8H6TVt2(~x+CW) zPwCV!CIFmGcrqqv@4iA9&gK9&4VY;d$UyML2OJviqst#)4;%zEf<2l@7_g90q?#WT zK#RIZm4P^M_iAEq!i!oS*+l+!_A!bWZr3cqPXy~xnzuf(6&m9nX_nqkT!SM6{kn#n zYv(gPYMyl}6R>bNOW1POH}?kecJn+r8f;C=)31mJm?Ch~mq>zc;oj(=c-a1}3*R~=Yt0Pl2-wXa-dt|!8ii@GBh+aZxHEshMP7_ z$GfEaui%u@M-BqW(du#dNU-hFE~492`LgQv_qFdvIG zSel|Jv?o=3lOdkuv04XZ7rdf1$Cc=4tKZOAlH}U%c7W%t3W56-N>#cRK?u*}D9J+xzgaH8_sAk!+xx?#$$2rBJGq7e34}vFm$Da7;q8!eV}e zGovJ{tYqz#ycfw7^ah6?>N2*(4dM2p!|x$@r@ASSOVT>@s%`lVOOZ z$-duW`)EI{+{&Ds7STfz79jW<^)0!-7-`%sGFXy&R0@U}-xx}n&Fo?v*SA@2xdiL+ zjvUZmHJ|)@Qa+&IgBWrr0q;sI9i@H4GS2$Eo-DjBh~31TF2va`Neu>^SL!?6Wk;VE zYm<5v65Mi*WMcc9&jbI!YnX{X!)jE4e$2SoA<8q-45d5Zm~FRihWU!Y7)|L4M@ ziD(+r2L<~03o1OI;@Jr(#*t3EQ2I6b0JI!KPv!mXrydpoD{dF2^;Y&m!(0UkY+blc z6tD|(mNp&VGYsgGnfv$GQ7|Zqk4I(!7>y~96s=OJKV+N-r4Zx26}Spx*{PK}0zKLt zio2vl^;!J%-*`-)>;rRUk`V1zyiyc^a|eyU62Cq_J8|oCAaW7L=T17ZS<*;bRVZb0 zz$Vz+<~>)cKmD_(+FbqK{S^ZhedO6Wova3U(!=LK>fvE&uqv6@%S!`wG=0ed^Bc3O}+;s;s&CDDxqOJ4#f~df)L@5Id(*q>uH;e5z z5q{SdPrIUyj=~v6Ms1e-dEbpzJbz!=-qSCt9_GbX;MNo$vG$;Q?;%dY-u;0(2bXIb zK@;!kc?-BAIhgt-GdIg@+wIumwnJ>AMggy6qsq1BeUZFEI4;Kt`&%mE80xYNO8$aA z?b`W*Uva{Mto2X3SCoY=S0;C;JcV;Tbq{Wqdy+f-E0LxDB+#Ga7w8DdopJxSs#|8_ z_rD2kp+IAn0Hw1K64e{GZlJJ!7nS@!hbPF5^i;7O%K|wKcf6%2|MyMFfLDj zTMV4{9n(Ww<}oMAwat6rX&Zqd7pV_UP1eLTmvE)@B2wl3UC+bT9Jzpp2}-|iJ9ImE z!R9LTXE-q|j(x%=pNJIR6U ziT_qAmm223pN3pJt2DBota(+H`y59h|wDW6RF$^RMsdam?&o%-f> z4|1pdwRPsFMiLc%msD_&b1CtZO(VT_?<2r$71YZHPN&E^iu0dH<$l(SVEDn!XQ<%s zej<7{d$QzYiky%a1WHmdJWBIYy+^dmKJR?M=z zV)D>#{{rmHoX6l+N7H(B^q15&6FJU=(}BIQWT>5Ke|EE8uiNskLelPb;&rYEzZWiL1uR7e0Oo#s zHx@8e6JObBaL0A+eP>3Nd61dA{LWh}CXP5ND8ri>ZYEq$K;vWRz7j7ZSg2Thc!hAk zkFeKp$~Db$N3AMv#9wfq1?2%x7ch&yQY&tVC_)k<~KTGvhW15nRe)INX z^3>~L=C>q0F#Q8r{jzFVyY7uGU6K)K^`$*G|j-{hcxClb!JtvS?n) z%k&Hu=Bul4hFrX)w7hu%Zj^!~Q*N5{nS zb@a_i_oo+TQr;W7YFy5HO9zrZ=K3y1LA#7E&L>G*eRq$nSy^+1%EWVl#@#aZ1g#6E zqV)`VZSy32gQoEUqA&c6d+?~JgqMm32*5HcIFgDbJ=UN zSQlx>v#+j3aK|wlPLkFXk@BW8Aj-8Lj2_JIEX|A~zn(5Kz}8ly7wy&qI}`d66!n*h zY0l|O{GAY}Oxbd3+l8rezI3h|D{2y{a0|e~+2|kAOl6iAdOR`G{sF7DT>USK;6x>O z9_V0NpHzT;{K#9-%_tF4M zUE_FVc_Rc=N|!hr=;m5?-#Wk}S!!D}NUY3A7<}0#N`i`AZuz0X-Qy#rYv#}!Z2cli z3xU}L_GE1cu@0ufOutUj%|BABL7ocs}8`<_#{9m6_TpKp8cX&mON%w zE5GdbGA^ z7&3!>dsTe@hEl1~TvP36c~~LqliMGl1y8CZ%4a9iYH!D3 z^%g%M-Y}0%(l6KToh|yjr^s09kDf`*R&pDw3-waCsO&*6w{W3!YoUc@oDc_X5B_+d zLg=fZjqs{bn}X&=@kcN#3kEiqHFn^7pf-6jMlfaEQ8D1VZ$KJ3O|4XZk^$6!(zpPc zLxZ9Tgx{4?hIq$*p1BqaQZwv?g2#;eG<67ScW)I1!)wlurSmj->?Veg^xM7lmu;Ao z#{%gPu(QDJ^sN$|JVkzs?!708=CZ0O^Q7+JL?XisWyk~X1@=dtsg1^Nyp4jMyjN$Q zFsqwSl%W?|oN9V|J|z7ztKB(`CW@ptKrMc)p@HK#lqh{mLhV+KVen{40xCT9 zAMNlTU!#v(G~a_C2Cql&l{UL2E+gXgL5C@8`YeMVwnA}vYhj5cvE2K*dixd(rjH8e zkqSYr{0!P2itCeWquA~E@xc>Q-_r(IVoBJh!N^}RG!OioIFnz=gH%?)A%5n2paZ=2 z8)w84!w!4&MqZREa=Lv-u(-XpVEF|<(Dm(2_SaPTygV&yna5cli_IFeoSsL99 z{q|QekN1D27OchU#g9ZyjB9$3ZPcIaTH=mk-R!wUH*3o2KTUev zym`|w_({SvO=yF(`(hi7SlqYj1i{*Aa@F;}2i( ztjwicT5;Y1gKF@jtkH22+ba(hSGkq(@D&$%bJVjHRt!Y4xQEXN(hJZAkSm6Izf=F^ z_$tBh<$K^}H3$){!SNO6d?j(SLzrH}*%(Auk%*&8gh}D7 zWL+P&>b$mnvg|*$SP{6~onV`XqIA*YOgtSzR*0BItx>KAY?2xRpJ?e_PB`6ybj~*f zXIHwKb*K8%qD^ldKZMc}J1K=V2g%bq_KP$5yTpL{l2{N}J6q=CovuN}ZYBXe(=ad) zz%f}KfXWa=QFhz2`GU3#birT%}y1E>li%Ly)60sR~h}vWck4T ze#D|1b@JYsXJCceCF(KHZ_&hZ-`*1IlP&@E&YJR_wp|rL%bJ}}dxLz2hNSuN#!T$s?+Y)G9Nm~@x=Udgh$#MjOI*X_74Bur#CDhTzEq!ZDeSra_vQm z^7*4quC$4f%Z<=xKmT0N=3RdgnEAT?%;fudR{Oo0gpgX?WMQCC*JwxDjPKv_62)?a zja5dEu37YIdmgw-XQ>qR+-18nO|Vu<(&2Ov*Y9m(S4kE=?1g*`ywJ60hj>>$Kbz)T zz3$2j#><5?mFuy42q>@-tIT@C2}3gMb*DQT)|_1QJob4hpm z{Uli+kX-)OfV$Q;)Qv(6Dea0S&#){Y$CSQF1%maJTEV>pPf8Jl(Ai2BV96s(}XL*B?R?VCUp{SvkQ} zc@ASHo#YRHDLF_cM>rL@S12hYq9W`Zz?EXD(njUG` zWwCHQibq+SQcYf1dhf^HPm9wkSO%$wkndZ${V|^Y%Yk@1{-3iD@bf<9+m({HF3fdO zwx&NPO4M{Vul@Oz71ehh_MYEkpe+kBVc+`mz--r66m6k$E7L=fTHbuBEcdwQfvA={ z$x}K?-MOZ0&b-dn0gKIb#j_ggrpH~+zyI+yUp&Px(GQc~(a#bB?6lnICOEQE9nN*6U2YDQ|{; zo+ya6inG`bqktg~UML21>!iQ&3|E}g$(L1;zy{_;#HGN(29N00a@$Co&kx>zm`98H ztjK8ljy3S3<<#Lmxg)H7soc$tZu_qm6$j#&OAwLD3Nq?$+aJ5sKsb~~U0m2CA|)!&{>eAfmy)M_G$zAMt$-ee%RXO+3M%5g-1;0*Tbb{~BHrVaIfnw$pEhfec(73I$!U;Ph zQCW$@WxoZQq!#3@3+BPyB_c8fC<7Yu1iuEKHH1pNFPB0S)@2Ynkk#Sy;Jmt6qd}&| z8*DKdU09xc&N4)t?08^FsHR7y!)6ZRPx)5Rp2G050y;p?^2jGO!U0jdJJTs9HK2$i z9PO=)?w**z#$0WT+5$)F7$WH~S$$Ri#H99TJ4o=H+gKpg(Y(rjh-0QSdM$m8c)U!2 zU%zz2N+(E><>t-6h8=AVZ!)JVN}*;^F7bXm7N;mYN7_riF3GwY+2w!j-1zdkyCBr$ z45{n8V^B}f@UTqPW7?LHyH}Xz$-RaYTkoB}ncv;`y0n~b0S5CTFM=*L>XMqYeUdb1 zx8b4nG7bgJf@N_d0UiPwiCL@fY)j#FS0B08-OpBXL3t^=Lu5XR+A8(?Kd$kxI&G8T zoSW@W`;Xl(=j>=kIlSWd3!^^@_7Nq12 zIpfYxQ%*ST3i$UTD%eQs?cUYC&X)fQnn8~9M^H?L6r6~AFX2yPh3zQ#1 zw36^nwAv&e3S~IhRuXh+U0oi1tzjRrx!`x8ca<5-=WluC9(0C!d9(9p2-fOZ*?~+C zyn^~2$yWNs!#vT^m_6I}>qC9jAA_?O+7BY+7BZv^E9L@_iuS@Kzt^ZOL#K@bh=?c& zDCxD?-nO6waIW0g*iTcDLY|z^3dtXbZAp|nKJ_Ob~)5P?{lrq`4JV{QGXnH zF7)udJeI03X>~+q+e(taaAec;$=ttGr{eU$H`Jfdt{*ITX2j?{#&EYUFV2C6Eg~hXESOpO>5+jl!5Eo?G`sl%GVRo#_Wv_dI zo~YXejsXY091&(jtpB^FNFJ1ZIMIjAU^k+5WM8sqa^o+|pCre7UrBAeQIhnj&%y|sk?v)^M`b{KIXezKHmp4r4_x4asn<`-j80<_dg3?H^-K$43Pi8>e2q^Z=#NNX8zrR z#%D4;X76sU4>8QtTDVoec*hso6tl@Cc87}4#C*0B!%6k8=KxUy?0=FzuQ@jNTT$ZuF`&|Jh-O4Jvs{}2_6LLR zc(QMxPSyE8UASwAF4eAH*F9fAf#KKxa=1K>S2cOnOBq(QD3k4->{l=Ig=HU4ytR;A zi=f)u+x=$M6#4D%O*6DO_k9i>!)ded*#eXIdp_3(d$n2=^9ZrpUp!~u*BrBJ7U71+ zfsy2Pv{|#0T$aa@t<2ujK1Sz~T2k&HUr?yjyUx0vO!4U<!6o%y>hWv3m)$FNt65IQzrcy{{j&R>>#_IM zQL<-#PWY>ZyldODIM+<{$=v)1_qOd!X189A2PO)kvm2QEVF@X>mcGiun!I@*juUno zCMVieGQASI2J*qpdPA}~nN>?Ipr4+bVUZ3)Syy|LwzDcfAs*v@FLJitTAJV|U{aPZ zVOPG@`arrACC~To`GlOpH?5SSh7!{7#3mshF0OE}%+T5uCJ(@ApbNOcUKDaA)D-!Z zXV+$)-`+S6klIT;TYA|EOw17o3uX#A&o}6A``$iq_!henvn_j&goMJSQVFaaw5yH+ zP6F}G)f=lM@8Jw&LA@%5fu7xKy@GtO-!jZI2JlB$Y{>0w zSg*~JB%M4NE7L>M=g)8GAcQh>@Y(wP@uF*ll!N`~1Fd zcbO?s|1{=M?I~2KB0mYv$;?EuLv!Sck{67R4;YzYaG_z-6V}mT%=w8= zG?uLK&CTN4i!Ju&0Xoo9L4}-|gl0d10RHKwd_U%7cx^IM;*D>GkbI`k!e&PM+XJrL z0Ug1?Mgcy##p^e@#Ga9ePqaeDM<<2R>5tdvWm2|j={6ZUr>A7%v_`EiPQ&Po;?b=q z`{SoN+eX)h_iwEDod46H^s|YH!Q49})$InfxS+DneB6z(bcq<+cJpMI2emkS^)|ej z+}~~!G+wUsxIEGAMW)d>lR}|hCMMV3Y8i2pi_W#D3TkJK>BdYP^vQ1Pp!vcW#uECn z?;Fex?QRJ*lH(6_iZ1N~`29U-+r9L^AcYMiFU0U)p;lGvh z@T8WH*K;f~IEJ5@#y=D1aKi)wkZQPu3<_98Rx!7k+A~%gAB{Xy9&n-W*q)Jxc0$FUdst3}fCGMET~3zQH5!NSppDm(SJnEAfz~2F28f zW6k34IM~H)c3OI=%iK`;z~66bzd1V9^V%$Yk#CUTx8cNUV(DPp{L}D7HM@oGRFCFd zHo!GAiYXt&bzZ<_=XEp2X32`7C%v}YfcdRp^$&HK)T}w=U98STvH`ds$PSahT&!aB{ZFch=(u`%dXJb*e=i-KvD0@czs)q5CGxQB7Bu z!7%Ir*eaD6&f_0+jFh1**R#L+=o+oV7A3=PHA+i^ZTmAViFcQ(U?0^^IOpNqa-EmY z!YDAm=Qv64C4`)h&4XkOWBzTfuBU{3?(#L||!s13g`sCMH) zl@zWx0l60JeNy7C^WedN5*7684gNN+b}@B<4C2f;+)yDQ3{&IE^UOidfitc{A%h}Z zu9~WFZ>^Xjn+#0XkkhWW###BF52&xa1J0rzELvh_BN64ah5CGzTLG7XIUp@6>Cp4h ze7NCVq2XbpztqDaw|>iOEo>=uh3a9ZI7@+17%W2d+WvPC(AxjMsryJgPUVE$AjU~%zAo?<+;1y!SEHd?TVAi zQXrLEbsk4v=gVUKN9Vr{k1MUiWYE1Ql%(>f5u2Rz>+RQ#{_8ALGY5A$GVVg->iEo} z2P||}G2Tb%ZSG%;N6AlcVrrLD)wJ_=WIcQF>!Qx0KEE&GLCn+R>2F-cbPbu3gvT^D zHbzXW?Crd3s8Npuu4QUhQI91RJ*8=M1NYy`1KISeG35)JX}c!y`$TD(Xk5%Q&kpvd ztgIY1p80?>8P5yAUk+K;3AP*U5C;4t)Fz_VaT|WGM<$KKsc%~3^ENa78jKfE&ouS5 zD8re`&{>q(KbYhC>(VoxGZkU&3vGMt0iXS+JyJK#swlNgrgt;;?o;k@Tw#qFmcDky z>LK)=T>g&k#pwtq%*jFt#d|L10V)vY48X?%FI8opr*o;`ClhKnHeR7{Bhe@zt@F2R zUl@S8yQh67P}Gxf^*;2QP#M)*C-1Bvjz0=QL=voxqd)@8Im3eL6LZjNEH#|i$Olq< z9ix_ojpt^jtsV+om@4o1xX1ZrsqaE#4qM*AVieC|--Z6H|L^+d;;y1mIo7!2y@f6b zxOUjZS#H%6R4VvQZY_>L54{yR^X44?TA$LEm}2X^gH_rZ=RbD9%4gGAQCn{w zwv)8wv-z`M<0mnG_7Lcu{0o}{fqD(A3ChCH>h*(a!k=IFQkb?@2qR3mWpQLW%HG7q z$lBi{`?v&@4jUZ+AnhI{ZxWk%oqUjawl}Z6N4g5<1MEY+PS$%_T}X!gx1Z1U#3vDc zSuB|&43pfe3zUMY4HebE{vnuEBQQ1{eVWA6{2^_lGLv|3o+Kv}zPb@}zvS^MbZ_*l zWXd}jq$Iw_55N~Z7v566gT+mD96liQ``=XdpLCZ4;2sGdjP5qupNUQFb05W&V-#Hx zpTB;7^%iFk0(I)5S`RgQ$BEZ@{WBY}+>6p}uxluo582zu+fDI-KIgHY@#pNtJLGK4N?%dB> zrf&K5%gq^V?RdxMzW39}i$7WN3hF512Y(4wvmD^t6dlrqtgP#9)kQqQtfz-hAG}VQ zw;{-zvhen|1!>DO_`c?PHB*uF(2lca*6A{401+{j-Q;lzDy5acZVQj*EA#ad-4F?p zZI95VQ0v!t@a{Ivl?NQYm*wo>@Em;0_S5bFe^ogHXw&T2GHTvy1b)gt6R9o)qF?`} zy=@k`7mQ&eEXn#H%H1u3)W+H)nti?^nR2F*GJ2Lj`czafedv-`5o^Fp*8aSGzRptc zTv&%kTGO*_DQLDb>94g(sr=6dQW`?gck5zQ{R?W9^+w%`L4eN#F%d^o`?OTEnM$G* zm|7wSS8OOE<`rN9_NX=M241x=!gyLc6ZoO6p^caddw^_8_=VLSKo~Y&7LG> z$+Xj5e(JKUe`6O-3guWLHM(ec{_ToE3H&Kmo>Zu*?%vv&+xVViYoLgu>&K=6!NzC8stw|^oIA@~JlAN$lBDNIOR7Q; z_p&z@`un}!PdyabJIQaARo%)|aAq}=GAowt`wFWEOa3nc2*xBAQ{ zeE7HjdCuQI3FD2%A{v~o8#RjxFIhH(W&>wvAB$fLIV0n6v;Hjr3zg8%vHoioy;b$LMR zoy$5pA78v&8{~OW=fK8pQmW=NryycoL;KUTxFgu3F%pXixy*w+Wv|%$28*)tR|C5@ zvE<3!h|aj+M$o0YbExhFW|v{{s~LPK$`Nvz_0x3VPMLj6M6oV0-FqH?H}?P&68B)f z#3&1Vk#luRJ)i$ZP8}U6Aw4zsw~KqQM=KEpcs^Jxo{p1Pb;LWGj;^c`=V2<}Dzgpe zV+w0d?_F9%Lf2n=AerOOgNopt#6n@sLEV#fT@?2Cfv=u;PN-Ea{m5Da9fKAFM1NG2~v=_U>Ok;+y za<(3lcxrH_D-q-hVz^Cz=1DYykQ#vOYr7{)xW!3^xsxS^X1zoQ`WqW~6y2LV*>o89 ze2K$bt~GsKjB2t2#itWLM!tt%6^P%Mg3<-OQI`oc_z)+jcOy)Hsc-LxKz{oOzVY(Z z_F&Jx{a|0Dy`q6EGbJ$}upB>W!1+O9_w-xyfcM%XWsdfkHVu76|r5_<$j$=3Z&iI4GmRoFVINY004_O%|C=-6I zg^QJ-S9AXv!t@i@3VJPcnXUi-ECAa!bkTW$Qtx1dNFwSF(5|QG{@|E?UT?1IjxMf` zw;A%BBZZU%#2x*o65*TuZxWWB@nvF)!rDg1H~soi(n$~6OlWK_FQp#IPfFp?(lDxw zn4q`Y-Fs@BA#0*jMJIa8DR^r1+mYv%Yq4zZ75O^Mlx4gES;1xOm-s8Z8I$bGvR9!u zGwxh;EpH#X%FLWUtuUG|;9UB>v=gFp%Z?P|hzb3Jr$)biHIZ9VhX$N~;}VQowyokK z)$+N6YU~Ge@n#+~4_;O=LBVdhx7(ciZT|HYs)SZ2y=HfW-Jh2;wCQ?E}E?uQ&$6Cd^48sF+84*Qc60O*&zV zs+58v)}aS~5&rz3nDt$ZY^0puL~^bah{IuFzD97~f6mX;!zZYL)LE-Zc3_9~^l7>r zds}G}oec=B!NU<}kdBMkB!p`-GnKjQL4j9^D)z5VqQelwWSW-2bA4)6d+Er3IeyT9 zW;rg4sYL65M4yd|5F}B~AEK>w@qOF<_;*}b6TP(Z5bB>yPQ6X~Fz)UlR4Fi%UsvQjxKtJo&ilP< z;*eGL0W0JHljp|kR}%9W2-w6r>Ra`}mquTCbjGgo*>*U??hilYxHiRiRwa6-ZUex= z9C75^>#a!Km3@-sDq z5e`}My&S8D2bYIjk<+%y7t}JfxVja>6x8&kwi1$NVUc_R^c5~4?FpL}F3h??D6^09 z&NIX_J28OpWzf7kO3crzfp>v zOrh{T88=v!zN7U=`P$HlEg8|vVI_WE;&QUd$9|g z;>&vzQ_$UJqqQ@@Zpm$-C$^?Zw)^hwpD2M6AwHNdk3T0y5W`{FfN?V{mD;XexMMRd-h=hIaLt2t_RS}4#$)B+1jX_mPN`(2bl6el`_uZ$;hYD zSKl`8$}b=d*;41Km5ydqYjlIgM`miifA493k}I8l;qOg!Ny$QsJofrJrQ+S`#O9%) z_fWffLc0v}qbAKU?&0J=fve9~2ov_vW}T(>b8QNdT|hGPtfxlX@n<3_9fDY=qkr5b z7dF4!pMHX92^ypBFGih&Up>LLUgh2jT&651!L~QIL(r0%jg>%5F@w%8p4(`-$D2SA z#s@u>7KmRS_Qwv(T2r7-jTjIcfk^;7f^{a+xT>tkLY zJ015gwN<54uyYR!$gEhI`{q67bbl=ubNRkLt(;+uGq;rcoi1J0sK=oXoy2~293{0Q z>oVH~M8vXdnHTTTOw3K<2YY8-#Z2nn%EU>PyH>=j(ogBw82NCGKequlU85T@xtyvg z4%t<7m3L8;;Zded@vC-rj$Sw={S>$R{qwbX2>1$3ube-0Q;=e@E2 z^ZuHMSv_M!M7y|G+btKY_#gisMw_9ty|m^xd?oTQ77$8k{P3IMV;d?C(BhPN_s3G8 zMM82dy5+=E6Qra)hxDK8<5@H-m$jVW9N>6b9DLX-uOjckI#Et|r%sEytt{f= z-`KTGuf%(6YU&7R)R?p+{6f!u$A8Ex>ykYE=ZJD#_kii(|7KMZz(V!=0>;*RQj_$` zD%Pqd$DuPg(Jq%}EPHQA;YrW% zH|E00z&_HEP*bH;I{G>11@)vz*MP+GriC14Xp6u`8Ghv%8g=$@TqILbp!4#$i@UOk zzzTAUnbJt&Tp6UJGEgumw?CYKm4-y&zomgU=0NT+{m;<;^u6@7v?vSrx=yCsa+B{@ zQx7(Q&YyTL=wxeyKJ%U52#R1!D*5b-fafdZr9n8M0pz2eWU;lDOyte3WBv@Jc5nZk z$M0_^c+o#mW?^j%KTyHmNM!lW`s=NsjF4uf6e5qhpO;TY{05a$yE%jSSNX<{*#)kk zOpsSKC=eI#CiFpySt4I%*eDU)#hllh{7x)80$DppNShaanUa!npan&Sjxr^prvN{* z37&O#g7;7(qj?8u=3yDI7=d6#ky9zmS%cuDrN$O0BGVp`M;Gx>3(HEI>Vi^G?hoL3 z{#6m1<>R@Mq#ZPGu3X=q<)*pSO((F@h)1HCteXFA9a>>#kWUw<%D|A!Od>ax`ERwb z;+c~r|2rnTIH9w0goEz|Wm!2>F=GGg9;ghJLV1|&>2ltbX9m}`3H`G8c}T^oZkWM> zVu!0#8N9Bf#~Y(_9~UhS)qrONaY7n=E9%rKQIDk8E! z?Gs1aDhzU7U0wYR_=y|0ch8Sza_2&2oh&{r_Dspxf+E0s-JCvg6Buj28A*(DB;D3v z*LX{AclWWXpZ@nK*n~bLi2iR`PT%G+^>7qd5G!}+$rIyW*0Eg}0prrrj-nJDi8gT0 z(|)bnDwa0zAe>>ERg_+Lys8{1j(1wz*6d=%b@kSbKWY*gQ^_@E=W7QFqZ=z$xHa+!ZoB#>m}4o z7TiJ_T4Ct}+@vzN>Vy61Z_oPZFGCZ6DwG2b&R95s+4aVtc8%K7<@>BbpMYtLZAxMX zur^MS34)H=w?L&?EHdpU<$ML8E`6_wi~83^r$X#GMGZV$1eyJrE0Vds;LW^pFUo6M zohbU@J#(EG)V-k0MP(ptO7BcrCA{q#_R*@}*AG1^%_H{%N*fRNFqp$qEHP{DP;0kS zHt$%HAXGJEkHS}CV#coj#ygQ48z;PxEIt+bwqT~1iw|hU`)#dR^J&VMvg|0X@W&XR z#LE6Z_P#PM%5D3f21P^!0cqg?BHi7kw4gK$O1E?kF)AQRh!WBusibs+l1g`jg22!< zbp7|>z4x4Zu5f>^{;%#U`Hb^Cv-a9+ui9&^uPYT-1UqK;3D=-#J2|GVD-jid9WvB} zcSP9awI%+HJT$G@Z6s86-$rd3l4 zg_49Z=~mv=$;XXY^F6IlH!Z)6tfqN1?D+0I5mTL)BvrR*Ii9m)T8kat+@R6vM}HY1 ze)I#s#@j=`B2d=_R|0&Bj?V}$W~HL7Iq*z}ttgXq$2J_iYm5L=0%t3UlduU@f8ljj{H zwp*v5`}pLV3RfGrg8-E!1Q9=4_Lnp2Tk716_f_V+$&&bdVs=*y+$LrRJ`h2e7#QNF z-r0Tys3p4w#7PsJ-20-iwMQ)Z@rAH8WdxgwnT_K8y%l!jgl5=1qISVdu2iW%xN3Jd zJ?bSrf>6in#P0=>7wnd^*uK%szDO=p1HiE>wa}Ta3sJ-m*vVHamGD_AU_t0o@FGygz z;Mqn8`&xi;-9TEaZfB8%>%) zMvuv}45R(EAc?S=y2^b1QBSyjO0A%V>zATurpO5sapB#ounn2=Mg3hFEkcpd<~usE2_Po z>|uv!PKGzauG)24MdL@Qx+eGH#+qh`ha{3M-*x?==q1d)>}OK0>$-Vg1QC6MD$eD4 zD(AY-du*-BU>B14?d3?dB!d-;yz*KgOHQ;5Q7OVNZP!M@$)MI-_uA; zsy{)C?9v$tu?ZR>GkXXFgPm6s6C1ASLltd=P0sTbLjzC_(fkB*mZnyGFc94Q%Y;qE zY}BL#NO~i#29x~sIQ?`recqkH>+9wy_W?#yVD-)h_(>jn9M#7uN3va#6y+T%eOHr?jV(Xkfp}9S# zva#~ftuzj?JsUpypC?~-L`8tE?~>R>2Q0tw|k{9&7v#o;aA$FwA7H%p_N4w{Sw1}ztO0t zmDhQf+t2uR1&fp4P5tPVBfo28l=84(#qQ9KtcuU2FCI;U3p)OTQD*%^gUPuiBTGEY zMf*RmP8ySy#t5J_c~p=Z-zs);YH00ZmFW1kt!?|oVWB^$m94YnNj+;TJDa4dCflOc zAX-UCEAuz0)ph+Gomw30j-qXD!eYpQ#mz*YLv9{kKF^lhY~&&D-*7F#*_#hstY1T} z$w}Ua)lk?4mu7gj$MB+uiES1P=_ExF4yo|bNUYuUeA4qQnC{a8E79q6?Podff~Tet zbS+G;g(YJ>Oi$onzgAaPT&a3X>51cTdL^)VfAY2pzRb*B-+=g3i~6nB=ho5pC_EAt zZmBpVLig^ang?#tX1dAKu61Y3w{!2@d3EKQ?W+WJ8pdFdv5?*gt!f#>OJj%jJNRft zdAr(e_$=OIw6%{HmR9HPMV+TjG?~0pecCq?p2X6Xqh;2WxZ=^JRz3Y`!Y|rn%);A@ z;JXBQK)6|}vlyK~%`@^BZ&@-yS&p+!=aPx#_1sLiCvKw4*H`lE205p3u$eaAc8$Rf zSkm0PUQg$2d7Ku`)UYpxoK)H+P)56m){N^+6b)y56oP+;9dzPag=ZYX|EXiJ;7j=`Z zoA~pOyv(@`c~rIpaa;X!!X2H=QlTzUSGYO%-sRhcI?#JqaNhScu_Ta-4wD(ih`*cK zv8Hdl@C=OWaw7QTa_g|YbUy)|HKcUc?B|s(`PSmZ?SZ9F?j!2$@5(NR)rwJDTj_NV zcfTNB`Xg9d+wQW|K4t@$==R)5vz0LQl~Q{isR0aqcLFbv@@OJOlgg#`9*!Etw1pR$ zbkjji*BhJn1GKMSqclWTzHYs8h{SqgR2tgrpUJA3gA%6Hk){$AROan{6sPg3G9a`| zjRd6|=z;ZWB+1yFk?JacYiRg0=WO(y;O^9OKXmxGFSP%b`&`BR`sjEVK>1u&*Lvn ziTt~a{_C1?o-W$=!W-vNA0&FlJ`A9qr5646#$y3FHYk)bgK_Iqv+H|yFh(|2Rx3H# zc{+&E^G7RT0?qs*lNWYm4G~--hFonEyEJjQl{ZR;AQXftsmdW)VyEszr*n2DV?P~y=J4a&AJ#Xh>8%Ai>m zQ;%0gc7@yJ;GTRVgpn0r9qr03@6E4_UbiG}j;?@q;t>A8Xx!-LYgW+)t~yFQCd2%f zgSiM^FxCqW2z2q!ug=6b$G_?k_nmU$g9liW8iu)o3t|@`U&yMDxKaA^(Kxm+O19Bq zzeOid+swO5W4&c-ytFNgA!B4j@O>P9;edQN9-1^4Mj874eBBZHy#A3jZf=6D;kR2` zQlT^^8i^{DdM^lfCWws{aMg5dx2##UUp_sOS}tFB5PX}JH*B7dho28r=!<@|CSwV9 z!v)t){+strTRS^siBMUzt8pGiMlzVp|40vu{vOul?RO(v^YPJ7<&F;=RO<@n0ftA0;#oBRx}mVyrs4u-8dF~4~zgI0C)PJ*RnFpq`{*_|0N z|H#^XnX3QzcAI|?1#@+o>5*94e*X4tzFD_ZxjNRv( za_*OTuS-b8`hZ``70&WpNnd1Xz6H!m+7F1vKj!Aa!9YV@E}Qbn%F3z-?fLh$wB|2$ z0vs1iP^#3=YdC>Do2yF@o5vcLcxsM#99>5VKNoJY4=%->f98H& zJk-Bt;4qPXQkCt4AP9U%H6Adf@Ak?A9ZXW)k#+$W4PADHrZl9NQ;IpLn>_a2odzj# zK7Rb1IA-q;#qCSD@l8rafCL9qO>4?8%eF4V5pOc-_~ysKyq4$;6{%K{_txZk;=7M| zIXNHGx@LD|li^_5oR}ZlJt}vyftVj3we0oWI;^r;aKa%lBHf`26X`uPCS3)iAi9wM ze$dGD+(KAQ&fj`Kp;TU9Snx}%+(~QrxT?e3MZJLY!gOgPWr_k zc=?cziRfzhf8-96`ih57WBslF|1wIFv=Sr08OQF_^mI%-vDt&<_}iR(2(37y-^}g= znU^i!Re~*_yzJbDSeFv_<06lDMK<(&RKnM1njaXw^AHJCdTv+LJyF8PZdG)t-O08V zKDxNQw`wQ+c!tSXZDcrL>GX(fGPVSV_hIBa0b4Jgeo)Oi;g@`Aa39luZ#KQIzHB#U zwJWq1ncS0*yADtWu=>THSXWhJDh8(hY{tG8TYkT4`@c`qC-uY#sKzd?`iZ@Is%%r( zW1bv&n z3`6^r_4yD?$S~@rGR$h+HfA~LIF6{YZxuzp( z*h(Ms%asok+F^c7T{=77MK2wf>z1w#L%+EzCcQF?+4)KUXp7N^06;W!eoE?)O<~>{^!SU zSSU%#YbjXL(X_W^U(+Vvrd1%oxL=?q%aZ&atrvqpvKvM6!8$ep1|Dtl1A_3Ym(ia3 z-6g@Pd7C&9alA`j^VZAU{H?MpUlsRZ;`~T^&3viTl$SX}7j#!RQ_VgA1r-eg=NgTK z-{1a#>!DSCU;XY`w>Pc0P;qFliVpECwjG}vEd8V!r{OeK-rZ+jno*Xas+wQ7uNli@ zst_w!B#1{LP{1nfDMMDY+-n&_FP{-k7f?2Ur=T&`>~=EwJg)DVstAOP-7}^vH{Ny1 zG_ZJ=sw!i)g>C3__(MV(iDWU1jFY``o~@Uc{F`^ki=evGjD+fSvzoX2<8J*2iT@z_ zAi$5UT)4c`xTTggg!1qMPnCzK=gOzUzHC*I*Re@nLffiEeFYgOmYv zdn>@G{^)wp`Olv|!fH83&CbLRU1esE;7XBC@_DpawmkN2o8fo7L=p ziYV)i>k^AG^qLc2hzwFqzn5Pdp_`$qXxX{k-#@6ltFFkN5T)%I!(;Q+RD-PMfv1^O zUGYf(dB%+>>Z)thTizZD6v42hY`2jlB-!_rXK}c% zFS(=i4u#jw+px5Ifj(j-Dhc{o{L+7y?0?ESPof`+^NfLpxeVByL_$&yj*z31uHO2P zDTqbGa?4311Lcl?zmT_X%H)-Ed8&0<(*o?uYhlKTVuusTvbeMIp)IC_NtL0H$R@SxBSKlQO+&Wy?oJ2h`~$N zmNK`IVz!N@+#r|M!g%#}Wxjg{H0okk$YKcp2i|DOSoEQyqh+u-9 zWaos-C3yN+k(J2&sLq6}7x5hPi?jE~oNHq|Gl4v=RkP{J0`7}3aR52&g==&wD$~i` z==42cS5@R@6UtOGKi`~s14U7@7HdHO6vaVty&vz;y;M~(eqKEC`%m7v4wm!G?9P)c zp0|CdXf;S7ax=isMbZf4gP~gIC&`5fGxFk5YOcTADWXn`(Nem7(GtRtOQ;~$l|pW@ zAPlxt%@}gsU^-X(YvJHX?7nPECP40YcX&Nem5Ov>;&i-t6g>8k;8hXSlrg|4 zSzYv!7EEG2l*$rT?c_6ghsgran6FWmKX-ix`2-O49&u%z${H#nEWA@knI0|~=PEp^ z8}JJL zA~^4uh%$7qpu#^t!;V(513|B->zk`eNN$JR;(#BIrG?9fseXX4;fR{6#6rjU)!P#I z`J3+v;hw`QwHCAqDnBYQsC$t4w+N;W>FMdAA9xawj+*DQ`tP}=s&rvw(drtj%r-kY zJhN30#iG@U8h)Pp*K;zD@6;P)1RZ^(PFUtRyiY@yVpUWW?kwh{T2G;l!S+e!)zE{eWTs$Qdd~6*CP6>}|1kYiqM6wX zdW?0ENQWwRtDW#7W4QuUq_w?O*x4>ULu(K_4dFo)LGBbAt?JpQ-NObpFUkAk1tetU z0{vnux)O3GpRoFMcFNd*)t!AQ#ktxIE4^!;YdrJ*K^Kw${8MXlOVu+HUSR}KyPJ@0 z28rUb#vYEgS@xFgC^#8<)?P$H2$BSf75_65QtDsiQ@;&Z-dm7ro2%V?91C4HWHP*Q zu4I$~4PCNb%zws0Wi(}F%f`=Bs?w#WCMge{454}bl)K|Y5T*+ZeI*0%!Fa{>&Sc4+ z~NX$OSCQCVzmCdHf9P{hsT3 zyV$+6NVNnrWmm;%$}{x>CTQ~jQIK^ix&dB+(;p{McVhXoCo@A*&nK;WQ?N@U{Quxy zYrBJY`12QGp1mVm;`GF_-%VBZ8Q-bek1J-U^F2HZue~*ZY+kUP^|oEy;=(urHf)9c~2nrrEDW>t?E6Ur_gXDq}nm`ErhxoT8NUfOs86y@8PbF35sE%iSB96GzW>9)J z(2_L{-@D zCE>X0r#Cd2Uhqskc^E6SnpH~UY<EJtrL5})gn!D`vS0U26DnO8;n8Ms97x}$G^ z0l+^_k7CUcp6P|8TbhF~nEO2P4+??Kgw!5%p7|9*tlhp%4;782cJ&@FeBt9}JCKd_ zSq;N=IX6Ip+yI9wTWit-sr2fla1jvGZJx#cEe#NET^>_8-Hx$5zyy)&d7dq-_BL1n zNq(gh15y}eiAf7ag@JGF6B#QP%C>BL;E|bmvre??eBT$xOQ$9j|o(3*I1EP&wKeiB!8*3>-!)+r4mbE@K1m*q5C^Y?B@n zt$KlP?JW!BIA7m#{vL7@cu7bLIs+{G@73K^mJT4^5#45-QsvG+F*rX$f#ZTR?fv79 zusl)*;e5b3w~v=gaM!dV6ET_O)CHdP#*{L5{&7RKkOtB`0#==g>tFsz#z#8Vuvg1O zXA0K~6U(k#hs@wJyVF*sxXX;7L5RvIXe;VhdoG6~zcljAQyOTXFJn?M5WhoBg22wg5(-Rnx z|IgwI-Y`KQB7oQfN=ajXIG445&s-z?U;|Y>f zD@kVJn-b?M$t-iQ49{B6Q57TwE_57-w})vzAcEE1lYTRQmzQ^?-VCChbrH>UWtcoK%F=I4or&DrO>Fv(>gskS@F zoN92kin6mTa$r&fmi_UkFCJYUs|%%^DZBT8pIdXmLgbJOaer7Ggj~o$#+jdcKQrw! z^>FEv6Q@KET4gblq<*3O-19fIdkjPzUSl_UsLu4v7|3fq?QV&W!m{1?@w8$2R3wUl zveN$IR5XGCmN8h`8%LJL`b&#Q#MLkBSZ?-Ur$yCG>3%0I*zvSPFx4q2H^>v8>!iXa zX=uue^gRILggQ8`b zOf68}^pT>=+m-&4sNK+WKXfEn*AA2~s)a25BPErR(27LA*(*D(7Y;Xgab?O+o zB&Z=-L9;M!pvC?nE3ihjJ;cuwkEaewhrYH@M#S3JnoWAz8aR}V`y5w$oNKYkf%sLS z4zM8&wx0xe^_dU7ncaW7vUZ!|{cQeFCNP%cd<=+Sp8YC_?ULgmyZtQB)nZ0(r!JoP7)IQd#WF;p*nv^REZ~%2^!eK));SRkae;Gr8#QKs%1o777vDe%cQlMO^tsjy($N5|@q%#};M( z?enfDV7f?IQtzuh`+@q$L@#XSduG64v)B97CvQ85=a!k9bN4U8_ZJdyK?qp0ZdUqh zwKa0dmU}Py?fc;>W#)qhWs%K6s|YO3KYrs7Azz0JZbfRYX=@?_9bh=`CA%#CM8>Xt zIW1I+&PCu(B~8a|Vsc?S_ncqHAeI9G#-rjK#WU(WgCvl~ruXw<8aR^$-Kbs*SMj};Y$nErAx{`1iE z%K+JC2zEV-_b`aV>c{yZhxY6HU*(DS{d!#7+PvLZHh1{W;{YXy!rlHzO*}gp2)9=OicYGc|JVi*jniSdSBD<#EWE;FZrr0lLm zD#UXkz1OStl+_c#B_Qzct3@sn*2K^~kJe3~R1O-1d!4#OwGQHhTma&ol|ma^m5r{) zY-FERc5YPZxMN#?{#12v0ca5e;BA@WE4{LU3u$i^S_Hv-gFb*4RjPJ43NRyk0WgUA zD41pwdauZ=<71_Fd<%doP>^gH(1M8CA~Cf@wy%zCKGvVz6Fm*)Kd%q3B zmU9T8>oP#GS%p5yR27(vV7+jw!f_UjfeN4)5v>I6J`p)0q(2cSoI>B&_ieouSbRwJ zVs{L*a~ArEx>dV_+IfWygE#E#)$39RP3%75$blx2C)2R3J@Id#A$A7=5csMPJ`-T1 zkwOyX{>pD}>bgnMMB~UR-r>=86N?wiUHbzV4u_pLPgB;hC&=*zB&Q=MPNcZLyE8Y3@&MoqCYe z(Ee1M-Lx4I2eMqeKNn|S^bmr?lXO&LsgqjfhTK_e`xa<-M;seTNHkT!i)53M>q-0j zPM7noW}C_Fm3;RasMSx7BXjv}M~jJkHwsj$r*_8d>(tfQ^(M&;&s6?!)zgVNm%@3)cJ`L#l_$-X}Lcbm*jeBx}z zj+VfQ(}=BE|AMQCI_=JJ%S$_(du)D}w{D@FddoKGkR5U-dOq5PhjQ#e6c|ch{v=+& zOk{auBh0tjalm!Uq3Z-LFRlP&O1H{%qh5Zedm`!dXdV&M1T|7-zrkI_%VC5M9>H2 z1ayRJAKb5;`10~4{0nzsDs5ISLJS6wOQRPy+t}?%pm~G6IASuc`tqpdR%6bkdF-_< zLe_mGgH?yCRV41Q2|DI5;SYsU81M1{?#>-WA;^uSw*;+u#L&F)Ho%s5okB}F4{pZi z>*q7h$AMadXX1rUWG=lUmJ8zJ;c^s?P_pvRUSlDqciF1$t1hnyECX1p%94USNfIMld7rXJOhK0mkn2ibF9`Y947=+FqXpvreA!M z<&6o;nMd}=uRQGq`wa(E|OuiM(0oZLXYGe4M8 zxU`{3c(vU4W!|Hr>$Weitin&Yn8PJ+EZG3GD4mB)ZS2BBS^AHI=+&LKDXBQko;Zsh zfZ-=}rUs7k$@D(Op?R5G6K|v4hFcikD}J|<9B)l*H@8O4g!rKAswE%e>yR)V%a(QJ zCRbXqf(Q$NPWt}!i8wi}YzK&JsoD6LgR04{ugOO_8jd_S=loj0_@Dgj%JSD3sfk|f zej~Ha>9MPUpYSPYO&PDa<~&ZpsNCZIScDgpg89B}Dzw=>gw36fbi`mb-}l&>3EgYp zp_Tz$T1IOxJS2v_$JD_YmulA?O5Nyn+ta_buw>?QJkHfsaA5a4JZGKzT6-*8LzCE! zJG_r0`=Ncj>71$NAldMthPBKv8mroP!p_pmWLuN@5s5*HTWO=)12GG-B*7kKFO%Lm zuZX>GTD9s2P(6pH8;bZk4_C$WW!=j16K*SNgpc>XI=OAO9pkbiE!G7SlgX33IMe9 z!WV|{`_5nj$PKi&8+1E?Vu%#=p*8{~AJ8R#c}#|e28~T8==&_T_}*vdqm7p6XPX31 zF17Iv>2vGlcGWiH?+z%4z+dA_v<#==_PKb7G^(u_X1nHOy>d|5qW4)2+ek0E-V!R| zgwbqY{v#kqXadX{Nk6dqJg?1!CcJNSSAQ$C(lKni>VWE1AXS7c3B0o_tF!dZQx(qV zlEJu!xs8QTqmQFP1|ME|%d4#bL`QZv&Z&`O**PbZ05em79?Og=UxY3CIOoS+jyE7s z5msn3;r@>lT;(@}@V#)vw}gj1q>IT+P9l%}g`Pr>5Zk5jkT zzNi%Bg~Td{Ry|DhT69-qVeW}^7P)=RI6zBJT#S1Q6%G%m9Q7PIA)e(GV<&M*U47-K=>hHdU1jAj} z$up;zTf+IId301)S{06_?s(3(Rvv_|#f3qC(pd&ly599#tuQauT{vX&O!$zwd%t3X z;dcrp>rOXg<@Y>_e9l5uI&FQO>6V_o6rQJ#Hb_QPw}k78Uet2@r+XqTRSCQONGK$3wsW>cgrh|WL82C`re z-sA4W9q5_EE#ub&>8NtxSuzr$RKcwZ<7M)cE)Q<)#j_rQZu@w-P>Pt2r~vY>o`OtT zu8(NBO19`vPG4gtZEbJ7RzO5tJKb%4+S%h@`76BnceY8);2?sE8@^rn0mtI=Sq^3s z+M-A6jfCg;kmj#qTl_{zx6SA zCe?Gbv1nL4z}TmKR=FvE3Xc*<+SVG*!s7Pc5Y3BB*0Wvu zdsbaW1YLo*YPXq>H<2dxOc*;y$D3mEkJ5+cac8O~84BdWJw*Vl%tKc%vKe9dvIznS z^+kD_%Dv7o4Ki3Q_0e~1rsgM|VH*<@=KUm7#uK)>?1#u%H;VWmv635wnb74E+TIDD zu8g9uZ0S_M*nF?J(i!zFr{hQsV_+v7PxYE))n4|M%7fhtiOR#+ zii)n|%RaB91(vA5{WgtGJ@fEE@CvN6eV6{fSTLi0N42*iMWj_-5Rot>>o;zCE z-<>G7j@NvBQ}`qI#P7zp0myi5cN9~fm7?IYQ1;&gWmxkyIN{Cip6w^Wvrqsx8`-iS zOye9p1)XpiimZGv0p0583m~@}PFKp2t)QpHp|XW%E!XT$G8Jl(ls3NW8WBkUv;y!4 zG>H8|_f?_Ogz0wXMR{-O&^eyUY>>=g1-2l~go-&qvsh#vuFRaF35M$f+82Z~US{-9 zO*u2nPN-f6-tjpco5}zHgvCVbLG3$k)iad-5-z3QxrSa_UOAfc7UBb#kAp`;_(rqh zF)2~uNJQKMM==!2rDk-l5|%N$D%+QdtdHXlZubN%*Fw4-GL&M}wdqd_)E~iO{I9?G z?sRf13r9w71UcghL$c^PY`{iGb7*8}uUZ7m_2~oa(VP5(svvgCeS7vsl>;qT)XXV4 zWu!ZPV}ta429vp(R$wAi|UxTU+O-KPPB16p+Ub4rc(awb$bA1 zlL_*o@toQN(Cq4-sy?euNzC4250<8DGe=j-|1@lW9@c(Cq=|dRTIn|qP@4OmUm{dD zF*Bt{%kQ~iawJQvj=@Unu5KF)w~5oOZ4PT2!EYbfA)her%9q)V($Ok%VCxduQmOpetr9G$zk#=(XgPLp*OrO^M6H z&!5>U(OLN;lxDq8RouYm2?JWgzJuA!=cue~)1dkx;Ji$-72GubAy?M0#k~LM^5dFs zY$e9yhkHsZS&isYoP)XQL|N^_9Z%bP4&_Wj%xi2L8`KG8D7np)9I$UVZ5VZw)o$lH z+Co()KuG*yQT10C=*I#B1 z`Bp1&Sm4|p>QyjAjQ?`g+5+}%5DFqgPf8F^`cs)rQ=EdPxu}zA@0&vl1r1jf8v^i{ zi>H2D02y+t+Z?Uiq2lp)C;WgH&86qq#6)sl(RsqN0gpco+48djJhfRA1)qdTK7$b# z^O7BaYsTjdJQ|y_+l%<(N9)d@?kW?Enf+uyBl0u_^uRzo_kINSWr}ToP}g*>O2~rW zK%+h!Zc!IudD6i-trK)XqBcMN#Q3NbDdJ?nDNFqc3Lx)p9Qc{&_& z)J7I^Zm!puoU7<~D1n%^sZV*bn_`t7iTI8GdM!8o?H` zL#{E-Q%@#`wTD=XMJUek(ikh?pY|4KQ;;^My8Uv7)oKKwFZMFT*}U-qi0IK&2W?^Y zl!w8At3F`EqEFa~N7gybs(!0^jV~cSpg8MSwh~W*_*OxDS?&pPz@`EK8Fs&AL~_$! z{BWeJw@04ZfK<3rn9bWnzstV886Hr?Okb!@$6b&byxJNc(mo9E^MsB8)IFPr42oFN zLt^7gr@&KW8fiL-SS5Us*9(S=4PC{*W>5$;Pr>diyRV?EoIVE1%w#Byeyz;+wdJBN zl|Tx|yNg5lUIBh#iwZ9Bc+rz za{OIM`|ajJy4BiCGfpC&3Ggfvoo~o!T(Ks~WJ%W5-MKCyk~B$hFl2fDb~r25skjaR z&n$YVex+f(TN*!detsxb!6UtigTOX85H%D8XU-s|hzxlCBJlbqK2&78UnAX`82>Y? z#qxGjw@KjdQr*b`Xx?ln2_sE=&p10Bj%4fExcpLuyKHQFTZeV;0&U;NaBVPEv=(-C*>)v{xc>F_`Hfwv2FqgKkBbGWe zxI1$E<2oopPs=&4+F`^McD-)xUtUFV4tAohL2bN-cK^LC!IcEa*Qc-%A)NyDxG;%n zr1Nf5%A?<~)g=x|%ZnIQSY6}d*JhztR0icRpO zws@h8^$kBe#IE+-Qc0ym6e=ZbbkC->*)8_~f(Ph>1IuPA~+Vyt6MUswgs{~pmyeTzfU^?3?upP#3Y zDAp}dz5gFsX9@4iJWv#&!c}BGM5=xUOoaj#BF3O{Z$r4yt0=e@9w1a%wQR*dJaXdI zW^KZB0uYn$@H0MT-oH|3BT20G(#TX3IprS}ekC;!f}CC>6=fhI@Z=j)<^$V@CYf{S z8FbC9bbP(pWqVc-wi`3T(>M^QMu3m?g!JXoye6 z1M>V0lo3g6$_TyeT@^#{=T%sRJ3^U>DuS{MM+{wNyRC`x6_ zHqYHccUVqLPqjG=GKT6Rq>+)N&uT^sD0>top%7DJ=@xXRjqazP`nd&vRm%Q5D;FWj zURbadQ??#|)7r+p@&N_s8*$W!0%+E$av>i}tqg`9hL(^T7i;2nH--)93?+?R;(ssi zYPY>3WY+$U$k^SS-lx4Q3^R71WeH%e#w00kkt$jH9peKR3J>r0ll&ItI^Dz0^xrW;yma6Vlf0a*E5q)yF-;0=6- zhlg$W^VgE@gB-j;CO)ZwyB1cQ?@m!t$zkUr;ws73%g71)7H{p2+lv6N^1g+E&lqdj zI9HQ2kxtOsGSc$Lu~s*8knMkWGWlb^C7vy(8izab^SXOhvTjgd=B7bL%W289*!@3U zvOoP}3ksM=Pj<6lrmOkpXM7)yrt&hKpqzO4DyL6*$f_2dF=OQd0AP=6(|PjA?udp= zM`%LlJ?<=&&#}a~sXT)@r$DMi_SFND84E)oS+mJbCRE2$r^k>{5UZ*a$NPXref8i^ zIx$S-213jejMrjPa4V^&;TzJv7)G?&2z%c{y$Z>?gMs|stHR4_i|V{j+w-e86)agM zNwI|>cdBeV*!3b7CXcA}BDOwtyuWmXT1=#7$zbh{sm0vxHPph@6P6BY_6+fjAk~P` za8n&kwyv(V!Zx;rehpPtC_^H2^K1G>8WsAHp9_s_azyq=*QSrvRIaNc3UZ!#VKY1$$kht@0hcE4VXHsg*RW=d`@o$cU z;4vM=ggfWr>=ThVB>TCAYHhS|BqDlx>t>sRNvGLJ5oJs@z>VIbbHEx@&JXve z79VBLY7~g&Ok52WUe&(LAup#B(K=cU;GY$JDGge1Ff^AOy(L@Xr#8Vp#X+iAvw>eh zB~S8s|IM0@Fk~vBk2CMMQ+Q4PvK1R#0C>NEYjL!Gv61fXsocgS@&O+=AuyR(&xnSUx?#mz4LK?Ks4)mrDG=&h|(rwxDuU!C&@!Ej2d84^=?NB%#-*9rn zcc=JaY_WA1(am0+us?K4-HCIiz&%_qnEcrzh2>!)=@xMb>8=Ib;ciW>Wu~V(C_mb4 z>qF+Md+nwv0OI;`FGPL{pp4uKr09)o7>)W*HH-o?yp2rIgnqg-gT0{4H8r!xsT!$Qv4k_I184&^=TfGpK^Wt*NWRK6CCF7%Q z98YA*GDrWxwq)I2-5_v&qg;W1>AIBt8>y2>uEiAERrK;sGXAhl;64Trc@;<0!*g#= zkZ47*?umGYz87G`8snBmD~y4X#J!Ip7AN zl{olHJ$%j?O!!tA$kV_;ok@At8Xp>XsWR0a#0OT#E8HNaCU(~38?{LNxNA~cEexTn zzhOYqVr{N~xPqJqnuepVX`rax|r6@@if&iZyYd>5#3q z3tYl|QFnDhIKN@vsF$fG0q1xsgCkEr5mLt7w^1v&)y}OL)4lOp4PkJUy?Qo+Qc82R84 zQq%?bX3^kH^=*3fq}&kTGrbxleZ}ho5A&+xdRHpY+3scIa9dc;?NXzbaWE6bS~{}L z=xGx&xE@Fb>n1{ud}#}3QWX!~1@2HO!EH#EN^AnX ze^=*H;Es7K;C#C6%$@8Z=hP; z760#?{u^-;%7Aw3FBXh{`~2_g1zlED|7~+(k@fY@dsO}l^%5wfqJN&9f9K;rALT{P zOSqJp@L#C+Ln-$9{R{qFjumov0->Dnxzh#y@tdd?yMJHw->LkcVEu#H{}U{5i2o;8 z|2QB2(^x=p{7+;3!#rToWck9hAl|$NpdfGoT;;MQv?Fi!z(s6X@p56?;OSSvb1L5d z@rFe?q}$N1Y|UFt^BObJ9;8R5y!na-H1#gYn3Eb_7}LU$9aeUXdB>>4MDBSa?Pe6q2*hAO}$8w?&(A@v7{=s#b{&pC7x?uj_jVz~Bl5 zTYoO$pH}lPpEySY$Hna{+ZP|}n}84CnkW74T2d0PwI;Fx`!ZgL-C$APpz&va62o^$ z6qqkUAnWDusFs);6l1yd0>C-L$pLU7@)lY0zF8fVcL_|fWZYhxs+t48xG9V5RDvz^S5ya+V~c`P@kf^tyssyWg& zoh|g^IbrWv3AB-hh6b6CvxP1YpgvIRn#4EZ+(#s*utwy*S~1@%I3LeF)r?0aBEVYI z&SlyDjuTnLuZX-6cv&gOa#qX$`Ue!&4&T|?*{Lw+l|R-sjeOV(>IYv>?$++|PFo5y zp65CJ>f?R$3OW~Ku8Ak+#tYc(iF;W_9z|r7%Wk~8(d^MymntZ> z_Bog)snfn}=;|uG#7YIb$!(T~yp6?mDdkP!ln z3S$#f8`l($CP2`#0FvO!kb~Z6GCq{4xcsov-zAF9^sNCQe5jxWE}kw+{VEA01KJv67d^Fr;T+<2?K* z@W$v7vO$GKU>|u25iRT16>dp3(yg#|V;G=Ug7o{!1H11)hLM*75hq9BW)W1R*XUCd z0#)TQ3Y61QrPnrdes$sNeG!}C0`;gZP?vjOXsFy?Rb-N*DZ9*Wcl-GS<$ zSg_6Iw`GREbXbiJ2$0xogg}9ZQ6TCZj+7e%T_yL;F3x6WitMszoO&R> z({v23^OTUS7V%$1cgNpuN-li)GOlEjE(QvV{OgVEHl&nmLAg8A)9Ec{0+V=fyBjO! zeQ>0?v}4Ly>sq`!8x2v(&CQ+gskXv?u{?Fe8*=44s9I(If9$YC@82%4Nd8U&>=u50$WjOq7bB`H0d>g5J*I%1_;tZ2oMzMB%y{9%6a&` zzxTZFIo~JyJ2PkId^2bN<2Vy0SkA9_%q+stUn^XP&MA z{94$|!yiH0t3*CVIY3X$lUNtzdU;GJd9M}oRcTQ;25$QMbh$4Q(4<)ggtm>9xtV3uxMKnG^>~i8wUZ_-1312);$Np27GpPO zX8S@3gF@7d~rru+Afd!5G``X>w&9PCZ5w<<1X~>op!bD8U4H) zHqEw$Fft?+9wiwFfZm9qcS4S>Z_hd9Bp6+@8*;7elsEN>#Wt@V?G1cg-ljkaTz+iG z{?{I5ed}ix(7VW9!|iUrsGWYd4Y6XHeRKtgQ1|xuHab3ghrAbXM+TN^&%V9#1~`d1 zK<`w($tMu#%JuZ+8|dOMXw;GHQ*wYUSUtP_X!KsfYoHM&c?Y~?*ZV-BlYq!20vDk4 z1bqncJ1_k9e@cY^j}5g7R52s?(kJY<$3d)}X~4;w`Bp2G(Ei0sd#)e6-7a1_T+()8 z1)q4RBUVIRO?Bhf`;FNiZ`%Xf4`spzLjakKPD5v)uXE99i`xzd=03!B401`;h;9R- zh$xb0Tdl6ElhaiW1?5^{gWUU9IsT&8jK@?_f|)tAqdQvm3_vUMc($T$xO?}(H-FWg z_3<1WKwDB4a{qGRYS^Z#DZ@DXPdJ~qYZXwFhindD@q&u-LWRixrb~L2#iWiwt?eW$Md|V7oa8&fXs6#roaY*DC-)x8Eo?3y<$dXRnj1LlV0xuj zv{Z>WzM!zgtfY4OQ{)gwMwY;&-yi3I$ytm3A_3DMmo@>{o7B12!)o_QIMv^*7gJI< zP6zJdgzyE9I%Y-dkmdnmn(m$+>MB!_HT}`~Lqep!6BkQ;Hagvcd!^Rv0A*f$SG^T3 zcm^QBJG94^ZUF|%*?V>;K)ChU$3UqbhX>*6MrsR-QD&H0T7gOM#zZf`BQfR&tI#6% zWJ>OzUvO(o$s|__*(EKJ0vY_{&!%*ZTQa)n_W{rJ-uHWKkMBzrZin>}c`fWW$sT`30$@x>R85bO^B5;2-lHQa!YuVn-d$v&q%!6A}-p zX)`>LEEeqVD^KD>7Q#hrD|3iIj=?v1;q4y>x~3;p#ZO(<>$s#G7+szM3~DC(;`dZs zc|Z=AWv*`9`qpznv2YmRHFAXx9J17YSc|ZDK-p6lEWWF>X@=MaJ2Xsvh<)xbd}{+Z zWiM@pr(e$b7#S7|M~#CrKqlaQb$u%a zR+kkn=WJym*=V4{ZI1g|MWv|Sbfeuk1?2bIc~Oq_G=LEcLh=riD;6b#WQ2^ z6YMc1wWUCydOYdT0fYGiIiyZYv2M2D?bHb=`I@z>9yP(1h|>L z5qJz-lRYRFP$)f7yyOWJsUF(7aFb$?W2RA;H}vy=O8NcQazZ%{_d!{d^jtJG9UG=N zEOs_cg*9v1MXkNFgp!(VnxpQWDB1!ti{T$1>f$^E#|4dJI)~F(Z^*9#eBPn72$M1F zg}H!7y2dDK$Bhf(s+1S2z&!TH?I#JS=&_)xkJ2eBU`zed8<+g;fGXwSQODgA=8Bfy zDkdNQ(;EMyp@6YzK(1?PwuAHaJ^dG;HLK)8KcB<`K2#5AQ}?kB%=#??ec%zm`vB_N zcx``>ifQTS_Yj9$*`wKmI4FjY;2NQ_-#MgdzKULh84?vE2 z_D$(Zwy+cE#C;B>GW#qLgsvp)nUq#zI19_TVMKqRF!JPPC=9!tmP1QhP5=lbZow$s zUV!yt_muggF(S`nyg8xSx{LQojAB|paY?QsN+e`!aY?ErMku8@R>UejgL2!hd1Gs! zxT#_<;R9*{yBVPNx|q88(~1})N#r~sj#YKEOkhocG$4etO$76X8)=?I2wmuPXMVe0Ew z*VNZ6-q^bJpOH?$-LCn7LfLTl6Lad2O3PA{D#Kk61~?A9g2EkM#@;voY0^mvJwLXv$K_Zt2CWaF{n!hl?Y4mY{PZz3 zr=IN7Hphvd?x(9L{!6@ENVE#6zb=BOqo>mUw19OKVQ95f)(oJG3@GTRh_=z5Tm&-3 zbSZ!zKy%c|W1yRO!P)JZ{uVpl+fOT4Qe zNQ{Ki?>?&lz30PH=-|yA;DV9Ps=bf}Fdfmqj+_2m+f|3dgf1KV%eilTQxG84IE?=p zKXy{(uS2JguwS9+fz6KO)IEGt)s1+@aippSBWB{x#eV+Vh(eqFxB%a%7memMl|(X4 zd*Exou9`uRhLxzTF^AhkQsG@P9-mx>s_u;fDh%tG*^jp(n$ukqL#m7B4D=N5?A_h{ z8Dh%2vPahW>;njPUta@ZZQI^uvVQ#%%GM-}mUM)!ohJLP4iA(Qy(oLc%MAe32`h31 zJCM2*M!eq-nh!Eu?ti$h*&HJ@=gXsZkVn}3Vz)4o&FZ)Y%K`S^{1bZdlqgG@E!)pp zn}+-m!vJDQLRY7>vt+kQ$j;V+e4-@yQmoph}c%wz|RQ$MOT` z$c-%;$Rx=F?*IHH3+o}#=fC+U`<;ea!kC`ede3G6KtXH`=TZB^_8tP|cBdLVMk;wq z)S0sP;@Sn2h^mAOJjYPVXPOrX6Z3RC);Ek`^ z6P(u}n?T3TO5X;Q@#=>67DxdT6`3}0$DD@k`H5S{6mJKtOrOAj`_`~QOdTU5qrTdL zEzgMr)kgzSV8pd)>|eF4bNN*ziaJba`^Kvt6}Co=XLQ%kU>b)AJ0Zbi)7- z*SlN@83hGj=ULudoeNid3*8y9Aou(+7vUdEND2pln)>nb!GvT(_#(X7p^p5|4~)itO{=L_H3$ zcE1)dnmsFV(?#>=yGgqkzE@ILSu7A=Dmg?YYb+2;UlIZuQ;+`kApZM<9$=u9_q$_u z{%qeDS-aWO-+Q~y@a|_Z;2=0%(_m3*JBTU@ogS49eM&>7T|rSuVCTB8_rLMRg8aRdN}>g!TkHV zf-kYMXBv2C;`v`)T1frGcE9>>)^=ADu;|sBj=_nMT(?j8F%nF)e(%Np^&_Dp=YT0S zuq*VJ5EIdW`N8sr1ja~r5^?K4X92*h6t@-{VU31p_P={V(c>pXS%k{15C41lzww@X zpNbv`ojojw|M+{K^k2^EFTe=5j_~gDZU6ak7DVr$@a9i1b8 zr2bsWs+iY zED=Sc66)I*dqlHxy&(x#9fW#}j#2x+QR?sGL;QSe?|-P48m!L6N-WS;nTVO2zJKn& z_r|6~f%N2o)<&9$r(KI)beS``&~C$L5X zG~$; zMD#7s(yR|m7dYnM95DRxO>X}L!xzVLV0y$2t6SpN0`{`&9S~|jy9s@l6l3ka&{BG6 zll;-tVrE1wn71<4hdEc76-L;R5AmO4qr#4fUVymZ`Mo<{z+@B_N*9#>Pzo}jRBcwl+1CoTo z=52?N{#3&hyPsdf9uG7glm+#VQ|Vq4%G|<e9v6om7zdq>E z1v*?8g$O*CCfnw065IO3HY!Y~R9q<+Z^g_|%)6B9(rVBV>M6B4JIuvTW5-Y~;#Y7` zD`EcnyXm_DPGyww#8obo`kGygNjGsQE-1Nu=3sT4wwl>RN~h(8%0##LsO0_}eP6b6 zmtW^z8~}_Z-!5Y%bs|OIKX~+#Q>v+Mty9}9#hIlVo)XZ2zP6bGlhq}e?asgK(V`b; zGYHjF?X?fc;TBTDeU`Mda=5_}NfUJt%%*0$O!ZBdW-9iyf)Mpgg9@<)hyY?{AUE|EdnS6PooWx)i3`b!`w@ zt1hE&9xP>i=dw$+pHuVJq+64dxyfNHK7BEpVh*eJ+o$pkZ}moUkt>kkuR)T}p$(3F z&yf}`qrXZWN)T@L#mA)Ir^ppa5>;d#EGcNSk_%S?D9&Ftiw<6HuXk8`2MlEO%mWdp z;_qx!xKHGceduX-3y}*#*6rF@meGSP1lq21$0VM9Zix;~`S1}US>cRn`bw7lSKP3zWaN!l|4^pJMMJ0jyDh`>QRbX z4QE*}2W1hZ0S)O!ndO79jvtyqDIBnc#rTAVt!73J471r&>+!O$&$D-NC^q@KOXs9E z>i%Rt|8dJ|v}ogCy9Q-zw#b_4D~TBa+}jGhTLV!-I?3q*txxzC{E^IZb@F7xV$-%m z-(O*#1Bgc|pRF`^$Eg^Pxnmo10vx0kA7{fv0ln!@VwiEtdu26aHfnGsJc`Kgpe4L* z2SGKH@7Vuxd7CHaHEcG;o8-y(2%Q-Jiq0Are%O_u9#JlJ`7q|fz8QUXjmE_zi9Z$I z>5Wu>Sq@L07|wKUSm@|I`JGQ)`kn+EX-ND1QW(n$)}rFuP7(Lk@*kw^17Jv=(4g5% zYX5l?DVlnsP?{v4vsS4b*A;L+l}P~~53YWJ_sQ@&78$>VrH^bZ{hHDZ3zC2bd_+vp z2J2iG>Wg;EGv}D6jz}jTWVlFhbJ1^%F7jY;8WLV)v$sLRvbS8+rr#ywMxOP{PkGfD z>9P%#6ia#gP^i6=P1uQkQbS(aRP!m!%N)Y?_DKu9sdBx(m*e~U-^co*Ctv8+WAp4{ zC9O9B6<&h_rcFmyS=`v>^>zMX~!%p&gz~~8T zR#-_R=k0??zEX)0b!>V$FcL}P(0QP94^gtTx(q=uA}Oci&(RB(I&q$nTA&0rm`v?rGJ%017+}B$+c#8-D9g{!gg71{| zOK0%VNEd18h`69RNODk7x$RuzF7?y=cnu#hL09(6ZUzq@XG%(csiZOYjLC4x;bQ%@ za=k+l;eTVz$C-{7rN!JXY%5a}-<1?eP)v~gqrKJ*6u)94AGbU|<@hpJAe&g4sWNO3 zZkZk?ASGA@@>Fx|unv&dZjj4#irJ>UHtV>DufCt5OXfC;IMq4{RSsX?`B9_yNOSEk zkkQLi*}$p&Ay4Aiw#-#C=fC)U9o@WVE(kv`hfpMP4OMCIqjH-)S($zf+@T z4pevsT^uK8jFWuLvL@=F@Z#kD@QrMN{=x}8F{5a5i7~jkNj_zqguJwRp(SeToG~_~ z{?cHTam!2TOoPVHbETB35h32dTCCkb{y87H$kcsdLDA=52&TZbG6eq>*m6D2z$6%B z@jmL=)Dx^j-`4fJ^?Kp;mv z1zTO`K|&ql4BewRa_IbupkdhXmXGv$sZArjoXhCiFm#t%7Gsy5r^HYR)ZRbtidwM0 z$$goNPxBCYefajtzfLLZ|M-p{aEbF+CUR0<@;F+~D=N5<9?hdFTpUNsP<;!|&ZQ=N zZRw2IHDa~n!{eE#9^#yWw!=+kOE%S0EY@d8$-`!U7$WBgAh@`8Lix$dFYr^w>|*6q z^IHe2t}x=|6}*W?NlmVlDxbW%jqN3O=A)qGHyml0sw+2}!&-%s`xDS;4zIf*^3a?W zS1a{l3a=;8wO=t;;jg(`KccdiHJDVJm3iN}M8_*c)do2@^+lDxB`v(RRdd25Zr;Ws2K>}`{{SU> zYHz)4&Ibn0%yG3pz^!*aHZ2Yv0#5xzPgZ4)UU5Njm0Hlf;16*sE`*ssljS~(+1M8!-Wf)A{V~`qX*4utLGO5<*et=_rC?59 zLl|I;htSm9Z7Kr^0a<^8BZ^M27syK|d78J8e~hoi#3Q>Xb+;@9Fy`r-&tALqN(>jc zW;OHKsuPPINgiUS&+hbeS4 zjzNBr>QYe>5#p!J?@pHMEezZg+}{;6aU9afTW6`IiR2xamf>*{mj{(oo?$nECgD<7 zntB`N)DhP1gD^J<(C(L6_JnA?eSH-FkNRZ&yh)#l+*6o#pB5N`zufvq z`WdU6+hg)Q8kGcUt;5WRC%(D^KD$=;B?%x_g%{7 z4~AyU@jejku+k=>N^v6DywzCshFr}zbv^o3Z3eVf9K^Ra%=imriZmU74oRhf?RA=6 zuiV4>FuO8Ai0Jx4vx?}|l3M+)$bD1#w@Q29r$f=>iagLvV4s!q@geDGx=MWnXEV$~ zLtZR&TJ~=MNvDfI4HeD#9{e}CJm7H(9zXZ)E9jdV+TS9;ryBf+k3ZMTYWQWef7S_n z8d`7~kZ&YxnhWgv!uK5B-3td;yPae|3?KQMXF4SMFk!FMd7J^+KPTF&i~gS(fW_Nz z)(g7t;oW-4j zwld5oAoq{%doMtCFB#SfZ!7*U;P-d`GrS5=sjsc>-)Z?*7W!5TcmchR*U@MG)>fI< zfU|6zqelEY82$iz4=_gFo1gnqb=cq9>PZe@tA94@Kb!TR&HDdi&)Qei9_dA84c!BJ zRzV$yuzml=Ms<3k{tb+-Z2ub=HJ(=MstNhDSHs2BkG=Bs+!#AHp+txQvI$rvQ3qc% z)PuION5?$yo^rD8;TUt7@$uYXz2r zwLy7;e&Y&H6==$NiGstG9_y+JytF{Ck~&#?r|yaETHCf;e2jZJwBCI)!p!@CJH>zd zrib~{dL)r8k6hOzV2`P{CKKco>Wh&w;+7Gdv`17mI`gc%kmw<<Ub-VMqQGUXni8Amg%2k`D3_osBI6CGm|Tb^AhC7J zTX~zBQIjWl)Qo=#7*{xC|f4JCl@@(ww z5&`<)?agk`fqS$ zU(utt158q%;Swd5k0WTbqBwI473A-@a^8PuE~g_l3 z^!@yXD}z6&J}}GH@eAsIfGgwnF!HUs2R%0{5#A6B{b1#)EN?dh&`skzyUSfQVb~Rd z>lNtAsE+fpvCBK=Xd0-u@4VZ#(afNV%=Fxu@X8$1@S!`$>UYp*D@I|P1XvQn!a70 z0a%q&y~Z3t1(x;6D-ZebwIyJTR^6>PMa-u(Gh}C+2wzd6oIv%x%1vYreD1ztfxe95 zQ`{%J#_dIc!OHT~?6p;xBxYwj6t-zsc{*#e{N#(LuDA(Pq zH|YmP_}gA+qliUIKMmTU!b<>-dH6nW@$kj7&QA%{he5e|s%6MQ-2+W>aD)}pHR--( zJZJ-q(%l2)yealeh2da6*xkNO;}(hc-`_9WzEQit>o`A9kbZPvTwi2RD|mNB*7VU$ z;(A)%+AC&V0!-zTlx3S0HW_PX$zLg3R_1V_(6w_6Rvkjr@*MaG)fEiSDXea7%;Ia0 z-Au9gY|~xwRA?q=f?BGSduySHEDw=SH=eE?wAcI*w@D5hXHG~P?^={O6#8T-h33^? zk**1;9K6-OH8RTO2Qx3J`!w!?oAF6O>E5bWNGaW^sW)o{twGN0knJzmFE%AL1fv4| zN-ueas)dwyHdE6dEjRY>s^O|_oo0fF^J`CcdG)?i*4nwr*;xoCB~RCAY;o9F@fjga zt}xmC2_=_yU2UI?6JRv7?&j-{T@*C)XXVXCFL$CfS=!a-RJaGXw8OQ>CrjT)NKFg5 zm}MO}!}YKQmD-;o=77A-QGh5OIZ1;YaN*J-d}nzN;q}`Lm~`eAE!l&ZofcM;9dhsO zOy0GICs@+K+$I^TW4F<3^Ol|(Yg)7g&mg_;qFEd*MF~d01Yr_n0$qxDxeO-X@bXgN z46&)g!P1ZsE6#kXAypiGP!Wi@tTlCrA*6hZ4ZIe{nJe7Svct>0fI$?7wy%YBaIxlt ztW-1aJ||C-c5r+SZFvU9wYS0ui%L=qqJH>#4pN~-DiLg%B7_86?ijVDAPuntEHpkD zBYfv&SOO=1s#m87&Wyh`$ja)WNhXM+#>kYE#v3J4sHuv3Fff+3ZF-%a;jkLtyz0D> zVi&GoTi1nan@K zx5VV&u5KYgF?Cs?4{$Gn2H+dMUCHiIRJ)i#qqxOas@ise)$onNQ9Sv^T7?lCk}?-G zNMW8bZY|F9jb&Ce)cDW+8gj_RJ9C?1>I3Q!+qEC8+_&hj5;7{LT7kPnG}HR z2Q%v37k^}{pnoJ`bcVIdTf*VYe;{okIx5fy;Q@fa|QrUc~g+L_H zdhzquM0=wNw@88K!m3Voe)>T}3a^f+;j(e`keF0HL(Q+-pw6?2)Qrk_gD>q+Fe8iOwd4nDbq8+YrF z5|7M^O{Uv*qENP4uj}YL1hVe*JeVOL=BTO)63bd{VxJH4z=Z6n|*DP!V^`leSEn$NcU?3x>(Y|hSK zUpLP)@=>gEIEA)-=(~XNB9zp}FCE-gCCk`BUnN4Jn%?kp=TN37EaX;Qh%UC5WjW1_ zz#UGQBo;!q$4IT+b7Z`L)cSnP)G45-{T;jUnzrak|Dx1o4}{L~GL1H%w=pkmJvHI_ zE>a}vA|lj>f!Qiv=mys(~a$MG+K2g1vuU{Z#6-Rf=S(waIOJSTV+jBAN{illN5iG-L+^-mM z5atz-k%uzkByo&sK_*vy2!un&{4FZ8C0{`%UXRO(p9w~|R%<|^+@H@>Th-a8ZtyGJ$R6dz9 zflk4aB#U!WkyK+pYcwL1W=3v4yY$?WJQB%a2>L$eTlXbP$VRNcxUK?X??G3r&wl#! zuswdd{%(jui6Dkd2r`=VtMM{g>8(h#>b(J@9+jNh0ZX!3{Q*N9PPPa^s-+-qxR90@ z;JP2*N?g~*)*zbp5C*({CegxfeKanFEq%+SeAsxMd5mX?H@&+9W_+Tdy@(?H3H}5P z#b4TKQT7T?)N>J}^mf=97f_TR4Bx>kiBRoYc7shWE6zMOYQXY|^jj9yZCNDeyL4K5 zmW3EAP2&!>0eocg4-sE zJ~T5fOVi5xBjl>n73x!!X>+yXN)atBQiFmmh)!s6uogG^VYu5)kg5E7Lo(F0Gpx^T z_xRzvoZ?)&2Kuo<8W|^2D1)p`yz$G-A9V2O1VnavCXC=)q$*(KNvgkMZAy3Z3w@m8 zwx+}ViA-^m8weB$q6iwBKbCuK!OiG8@A7> zEbge5V~%W5jaB#B`H(n6@J@gk4>*c^4WjZhy)IRp((TYFhrfwZYy}+406l;Coz;z+ zaPl2XBHuX)4Y$tbZ#>^b3PBm`4G!cM%ZufFmww8@PkgcTbEp0lWTwTD0o}fO5Q)&V zyz}C+nz-MICLx2KDQQUx)v~s1o73C*4rI>=}>XK}a6d9urRit|}bUtBh#K$Wi2)e;7W9EBhRkm46tyHOZ zUNn+9LJ)D(uw350svhD4A2lmD${bvLw!35_-DsAX`E?9KcFl<9nqp%R(G&jSM--uU-v~;w-U9nMxyD zuDmjyq=a=NW~j-#<~2Tn`}Bh}n%LiHbzk!pju~oV-4P@xOKw(inQ?=z*_kFRnibEY z3^WIv>ZoGHl_wiAc1_+6)XyYgE0Qd;U+4mKNo_VsX#F8w6i%H?_^QO%-sPKm1WnmHh!*xMIU( zbahB=aCgVriM1#`crK%Nhm2=CIL9Tl6M2M|ifB^Hv_Y7l>VzP6HIm2OCWx13wiX5? zba$`NIbMrxZlMRX@U@96DZb3b*ZY9336}w6cu7Lh;3}&;3@QFp2;7B)kwAI| ziZa1}*ChTUQd$sRd#5im@m<+>N zO$15J`|1R?+$U+!Fo{vt7KqYi3 zx%nH}H*5oj)jaza4njo}SvAr5HiMHcNL~cx!D?rI7RI(N;z7t)Cc{wwv!`wgPGeI-S z5o@m3L>!Y=-Q<|NHj>(Iq>jQP;dEcZ8m?M{w`lNUh#DH)=M*zbzE2B;ySiKX7S(@S zdSP4Y#wYn~ZyXajo>Yqb zl!sbI+n-G-Kl|MkY!_@saubKUbmD?dOP&U~j9j^eIE_eC(+T&;6B@3Sh`7|Gh9};s zBTsX&mjtcbq}PmjJ4k-t`od8k8Lz^)^>L@dN~^vu-FF+MBs@NOX2o`9_U^7ZMQ8JV z?&9Y{FHwaxOIMQu0^|A3wCY5@Ef4q5@c}dYA*8iP8UbCbpydko$Ovw8<+22GmM36i zpykc9&2nbK%1HYwkqYhiV+84ZhhM+&Y$6izr4GCuQ(FT(=uOsJ$}`!Z>oxYEM3W>M zNN&zQ3+rgDISmk{4$G5o3J)j;Ncl6<(Dn$~-)FhU2kTi!i*T5=ujbS#v4zoZXPz>C zbsvyy4_p>N-F8sIu;IF`;~_g+-&{vZ);nDJ4+CR6d<@^lorMb-KU49o52E)-%b0I} zL|lO7L^US7;NrCvcLmR56Sh^=W2!mia6K-DM2UsDq4*|_1OCj>LW^ynj=;qJoRn~* z`rS!BvJTu+=KpTtU5y)e4CSTk`=Cl{_?wG7J}M1XS8hyGj{nIt}x))ItO zvQIMK9gZ5Kqd{K7v!Zht2lA#TIe#Rp{bg*L!Ey6ru)Y@`;aZkv{twv(N}|@Ds-~Py zQmzfEZ1sA&T%wrywYF*n4Om$6Cwu8(y{^ldI{+~-@#qho{#)GYCJ2Xl7v)q`BP zh+>K8=K&BVbUn1OXLG&18vl!_$dT0RN@?RFMv5HO^;UC&jrOQPX{6Z~$k)qD)$JEV zB%_pU4R_?xb7}sM=C(i=jyawQPAjucQ`ubnbUt+Qj<^CoX-z*KY?*eZ%R@l8N`QCg zV@{0CRC@4Yp`dA%vzMQb`XAHkFkbf2p9z<$St!M&orum+C3VhPE20EZY93F$HS}`3%EcNl zv6i9=<_RnlHSfpWYFvQX5`HbhwJm4;CL_h9y=GpzTY<~UH=bA}yLW;_-C8p8y<{thz9@f?$M=Z*MM*B8Wi2+_ZWZx9Z23ydNuRFAmkkD^k z=Ui=ms`CZEhQW4j!R6wjzKU*i*u}vkC2>&Znt=em8P=Iz=#z3}GR=3cXn4(8@+$3r zB~)K&u((m3t=!&zzSIOKH%*MBNF?<0mlc#QAF~SZMy->g4ZknWn4tn(T{Sfz9}?S4 z5LlCYV^->lhAys8xhz57HKv&W=91LBDp%vv9*W6}N7@AnXDxrn7LtYscGlXSCDmBh zGm1YV21d_Q98Jd(_&-RM2xTu*jsZ+i34!6E#w_GYnY|}@^Ju4aT9spcU?C|3G2wi+ zVY^bG{V)?LLG#Ag)^~aY4XK{8-F1!5wpp%~Hf7r8#9CCM3z;yNX>7Y%TBWV+)8-BPECD(orrWRYi*E80nxq1z2ZtlSJ5I$@8n41FQR zXKNZL33>W5@YC|eH7BA5VvZhArM8ThO(B|zcdB8>ja9?!)-sm+7P+~Pr;yF$uwy^& zJ+m97QeNEK{QA=F41FQ1j`roNzVZ%KG6XvAwk(~omLX#IS*=hg#bNdj*SC+9nrU~! z!fO=}k+JPDN{!m@joOif2Rs9+0IuxMtPKiNBtVQte)$XI7kNMNv5Sy#mhp~fvt zFjsS43F+Lr=0)Tr`1rAK?U#|~1I-8Vgvwbx`?GIP$rTPHceXK;?a!NG##$Nt-iLV$ z=hXq83cERE;Pa($xzC2_s62ZaJ@Xo=4;KbHPtCyGDV%jC7ocd0agi&302`bAR^OH245Bn92 zbLpa}JMV7$rgA=~^k$A#^Q)ljlX_xpZ~mL*l@ zcwow398uG$E3Zu#|AB&grCt!vZjVSrj<`5vbA zr(f3XlRq`}a}z>p003K9oMN)%AG>+um{zJ=*I}JTu*MqI15GOv1*1P=-&W|!G>yxp zL~M#Uti;dBIi_SSUn1kqNtMy*6kT2W-R&shXE-|?De_2(Y36W-_KJhrR0R(J`VT*( zc)LeYHV((jc#JFdW2q*b*Ememd0Mpj-b9psUveBm3JH$fm~|VH#LFKnP3dJ=ccyl< zOHh<~5}lqe((~4iR;XD-P=ztGsc0UnL2b67&f(dSCDNZ$QqtmdUkj&k!#QL=vo z^8K#GQ1xA>XVtMmL)(-POAvY^+qLJBOo7EcH?sYKKk-t?c_aMT@JOfmZdNYetS;98 zA5o$e_SSw{&C4!irsvw@p+OY5dx2KD_BJw%Pw4z^>L6MIvi-sep!7hlcgx||87Umn zZtBGmgEtlO`TPw&AxhNij1Bx51*+lqU3UKnLB|O%109cF+1qCK*h&e$TOMYgn4@0s z4GiDm)Jwg7ve1WM;m5m_qcc~ivR)(?i^{2Ahq!Ch0w0OY^qrUHLjzSAZm>E>#alDO z+7+^E!u68^(*u=DK#0yHmZmB-v!`P(C`aFpuja6ZNVq7#vBf>lRWZCpF;JspBklQM zBPM8ta-NyC4&~uCCz~82ZcxYpc0{Pi+}y`xvF*Nz?TIU$QGoyZoWQy@<>`+5X=PvT zHR}G%7)`6`dIr*~v7ajr+^95OP2BW3Pw?yHk8>bW@t60>?w;JwOzsNGNBdTJpD0`E zT$YQh^6hl)DRI&ow*F45Gv>N!x%#bd?A>m+#!{RZ`v;x5#LjStSPjN;fg}wntTlBX zsrXo2=g!Ra$zpwJM@>n%h?4&1drW+hdhi%N;24>nPe8QR(>p`o5ZnP(b@D3*PLUxljVI=@^s<+(4RU!DDlP;YkweAvs&rME^8o$imfPS3NW zlbuB|_qM~n?2v}s5`|pPGIHvFS>vi-m>n67aVypwuZW7UAnMp84!A^3&D6^ccvbhX z_lk-dnn3Fe(0v?4-I{mlxexnqjICU5)@oR4+34gpDC*540x`o?kos`cxP zNSTYF&+V=;0P5u7fXCe&DdB6@o#s^?t)cMKX{AUNA*99!uH1yF8Vh8pU5>At1?Xch zc&u1E4i&Z|DmhY~>1t!3 zG?}F4M$3xnLen-oD6hmGEB|P>Vw>clc@YSRv%}2jIu9nTw2*UJ7jkB!K0$2Vg@}Co zbI1H=&T7tiGE~nOZrW5)K9ZqKa4;}_(cl1f0I78U7ScVgk?FaoAdb1I|J7Y)$ zN<8A-t5K7nC#7GQW4e?-I=ZQ5yt6bb;lES3>A8K_B0GP*>s3*}uRYn$LcYSW>rrwX->`+U8)wa5Bg zYSoVXnr`Zg?Nm)m#2FS$*6hhqhhL)oJ?sloeO@^h!%99)NWD;_ENIo!(ge zDQqwUpZW|gTNUDk4)D`lD{Gij!goP^6J+AA?k0atu+l{G6Y18oU@B6>9u1s2cofE;pxZ3)TE@l{j z%Uuh%uEUD&KjPu)CJXip^)*WWJh%$I)c+WFWSii06a8C?Md;~0e(-s=I|}D-R;VR+){Crp)t`8Q zCJfr+n*`yurd`lL6?&#NR)&^wrPpgI+y->>Q<}M2&8-(*mEt@~bCgqdv+u&x(qSgb zXwSP#s~*Oq>Qf~jzNEQzCG5#Ij*XV;B2>JeX1qyzm9#S(h)3=r0`*_OSg2LL(g zL?65v&k#~?Ydcu=PM6beSI{*De*5=*&ApDhD0EL~(bkP!zEv_mxMAR~0Qc#72gqgK z?R07Fez=UDLvd~k4rbl!Sxfxe*rm}1l;ygNdr8HxIyqx`=*WqcwSf~OkI%EHdTzbR z#O-N;MyYdoQF20e_FooD=sIivc&WW4E5=CFq2E5K#@)g_?gv{keJIjnfAJ^qRD+Ux z+(a&~CeYG*qki8Uqjv4u+~EhJ-5Vd)c92mx~USm;%(qZdJVSbJWHs@u7Cl z`2ni3c);dgF}f?Y@-JVU89X(sZF&sl@UdztM`G$KL5(4BcV$OMVE1+iBqt50vNlHt zlW?lL!>Ck8oOloJcIjC-&wlEy&|iC5;!@O|C;Dn1K3p!1wYX%E_zMr{<#K}FA63i^ z@lvEO_&%;BLa!8lZbyLO?kts;T%}S$u8FRv>N$e5)e3fiZ>cIKj1Prd*+K zpa>{i_A2rDJJ;g5 zHn#}hcp7w;W2#^}Wb~|Ycj69-J12S+d=I+w=7A>#vuu}a_N$n34FH5VgX*8Tqhuiu zm1iG{-wIy5@V?P!k5(0^7?e=AIpUdwmbKzsb-EOyT(4a-!iu|q1i1y`%qI&PwN$YH zQ>~}iuwL}K8o*U^rz}Yft2$$&QZVu=zt&oO->E9gAH|Ws^3-mh6-xZS6a93UeqVzn zvlvK_ejv(3ExAhCMEyy|3j-ckN&UcD6Kra^nZ3lwarPI=KrZPpJqUEFBhR;7u6Xt_ zX18*PNXRRGcSeab$;bIF5wU8U(ZvmnNEp&hGmC?n@&YphLIR#@{gJIU@qEB@Snd1v zhV7D$|9vKH$Oktq7?sa-%^065=1MmefWftjk>ATnrH~8gg;w95s`zr;Zk?qxAkJ3V z-Z+_*Zm=-#?HqR%?(29Zai+4j#;zZo<)P^&6U2d;kNnQ()k5ds_zF5pyIlq1u`WH%s4{a7%0x1d88HvMc%K zg>~w-PK$4;-giRW9Qa;_Lj6sH$r*aD&dJ9A){8qe6xLZzl8; zf`s3S2={1I3QkYBHdFLwrF|nxR6_`v72IZ8Z@PQi`3z?Pl=)vub1ql3edu7R+@ZD3 zqJGVXB}trdCI0rwzuaQjQN}Z?*#750)vgy0C(y(Wdb|BP_HUi}!*HJg9dTd)?mTj7 z*jTr5qnP>ujTQ*U-59dsy1wSjm!v*aVIt{BAG+ntKyW5nRI*e;(2(kOdPX`g5(fnr z9FSu&&Dn?|dhPE8f=PGyoysbTIxUav0qQ?!iAUR|P6h^h&3mFJkfa}KsJB2Zwqy@Y zD0L`~1Id04TyC?-(Hv>jmvu0-Ju2C{md_a;@`3NLCt1PhhwN~ zKjdW`!p#?2FOq7+o>gmRM84)4;0VdMZz=|Jeu(qJcZ>lHWc$0u++#S?>EKwbin|~E z(vNx*(WMM}2Uq$%<*`g!G(g&te;EsLA8PKB#%$Yz{v4!`$2}*oUhq2{Ha0Cg*fm~u z=EhW+!{|<~R97OCKo<*v+r$KBH@M?S%M+|QvF3f+61o)RASJFo^C{sC1iuQi53$m- zk_=JHhpbCSkS!4eT%9ynD~1p~;sGR8PQ0DF)fWKlyYuAlyMKZvC7%&Tn4T$*aOAwiP)J&eG6 zk(PJ1Eltj**9}qbl0+Dg@bY^|v2U6Qcr>KqSr%_MU&93?D{gh%?SiDxiNA1z-qKj{xB`^U_nowdT*Nx5Rk zw&bTyrk&BUSL{ODT{`uAI>Ri1K?wEVPJRtuAjK3~{_&uyqQN&-YTB+4?&!OAV@QOu zHGWlmqB+qXt&7;)V635+-r6H-eScylCw^uPVM-gOi2UYQ3$~0B(yBxjshNz_&nt-s z_=aDCB$qZ!pYBoo?yk+fI}??Idv9k1vJg5PwMlNTbeXD|UCalh?iE#3Qbl>$Vx@fp zel9)b#Hxx3qAof~4o^cp2d;x>L28u9;Zt|U7t=;^$Ch1#P{Qlhntue!XUNFv`et;Q zDg|?3hGaa#?M+L2=ZAkLb3I?{kpMH7Q#k&NtzTwdUw0u(R@Pc-MY~sf@DK0awbHm! zLZY_TAc{`$u`Mko!LF&l=!QPw4L)rF!<3*H2I~PJ%lFC-r?6fID zt9^|bF=nRhyHKfYA!Jub*%@XqmL%&KWE*3QQI?6BG1eK38NajZzQ51w`#j(KcKv>T z{+`$UXZ>N$^Ei(4I6lXF`=~M9`U=EJcUUg?Y1l0fmZrdYy&`rr^fyy*e-Y22Rl0u4 z;)sE_XJZsvG#Iu!AGSg-uSz7LbM%TQtS(Ruu%5p7@)eb!rH5GkngcEnp<`Ml7Dw?G z-0=)o=wwdEGpn!vNJBVow|r@|i`54*bcKjaMSS_h^Hx4-B^3Kd&Frjeqb*Z|Tydid zE*lALxdIXTf~@K>U#`=}a#87KXZK|bQ1O$-@>N{U8m0wM_55@z8==3eLS^H@TySEzi@ zF{=irYP{KVWz7BNuXX8ev;H7XQg-u;>$_;)c|$E#Ii*&+R>f#cz!JT`$zod1H?COa z20RTvBBvjOv(jmcm>4Nh+1xnDJ_}L(^~m56x=EG|1-&ho7`aI8GX_Yy!siHf?%&7BCydaEXsJG`L(O&%}3*xk{|ug80Ac7D*S_iL5`^AWW}^Z5?|15EVk@d6sCOSg};;h!$%$^Vz02 zyc1csIfPTHgAd&LRaKp!H{HFhd#<8#g$MxT8aEs`72v6$RNeTLkl%8^OGWTOM{4Bp zne})bMZc{g!`rV(;tV*Ore}Xi!FY(Qlg0I=JGukHxZB;DE&>A~O0BGdF|h0pRx=~I z!!iEbb=&40omDe-V({sVR6UFiirs#FXj{tH(&DM+#T*M@KZoCQBkXi&XHZZL z*of+4RjHA5o)^ZDR_~m{W9q__2)3DSZt{uI(vuflNe zJtHM1nA+;eGKkDC-AaR=dxGxbJ;F3bz)!@Dn^08mc6FV%JpU+93ac7^s7ICN`jNmX z4?)QV-^pzP6YM|XzjGR`Dpn@H=H_KlXp*PctK~R5V_VYgKLC|d%5(SKSNH|%k(XM? z-`cyeVjkp%`>c6j10dS#Ub5i+Rn}OhOa?R$?2!36{=145%*tw0+ zEU*K55*TY89C zVz0f4OMI~Y^+j?8>I?MR*ttzsWxyoccjk>?RTKw3IaVya^$|88%w-E?grEXO80cS#W1a=T8-@W%cdownpJwnCd9|8n?9OF-~PqDGQjt^19kUuTSMiAvzHXN~s z6iTi7FW4i#R70eB_@cg@TY|2fwwbMlw1N2I`}MORUMj7)oRyJMR9~{_W3;uhK5=V6 z$OX;J)cL@*3EMVPxiV7Y6oh<W@9veh%DRjl*am5;gn&an>UM#<6p08aE za@kvu(^$e9e*R!Lb#yf4j^(Lw2lz}k8!VmgoRQq-A_yeiusP6_&16WL zx?y#5PIG1+92;4qE%lBM}UX4dRv=j!}e-&j3zSDH)f4W3|UqvD|)FGs?b zYhM+|PFYOPkv@W3**peE6XJJ*xNn4pe@N(^rmyr>MTJ&8DJ)oAh?woZNBCSi4R0_n zuIDND`$o7wUoVj1*I6j~tQzbkkN)EFd2c(u<>0MyQiC0JoU_&VM3B#7=9>#Idc&tx ze7~@!d;hSiY@xQ4Yow*4F0!$-ppgA1pmPs$%l8^@SHnDFaFfh+H^=ve#Sl%la@_+E z?;ve_^+I*}A{KH>nIn&K)$JYR&r;KqU!snT`S2Ti69O@sdaQn=lbaig{v-(Xb~Lwu z0M40ZFHI*ee@rPrhFaD^wfsI{x5e>wKXqm~ULx=yw~&g&xs*z3=oRx#boJe}%D(BN z)`F}##n)gW%OKRksG*wk)+Y!lIf^^aqlScK(+7HgWNfh7byvh2AnUBkm>uwsIVqr0 z4{2ALqko$%^WBXZ^_-ZYH6$AWMCVy8&|9ePJm9*Din8vS_ZRtrjO0m>7aQbBWta3W z?x~+dqJ5Zi!S&|X35t0zf?NePB9TGtK5KmX&oT4kZfec22Q>|AJOu-^O(9!fuQ#bf zl3E`_27mQTD(^)OZ#+-kV*v7_)``AVC@bx+Y*4r@pRVz2jgIq1xczri!9FtJ;&Q zQ;qVsr}u+wmXfw#7~loZ`6yyeFqDaG&!sl;aVz6w7VWFl*iDquo-MZX5jzIi8L-iC%;jtegpDpAbW}y-} z;$q!50Xz;6()bq7^rB{OcpO4U^ZMAaZW|{G3vZ8qN*ii}dUep}`xNYNud*~Cd9xoG z<jRGw_EVWZU&9jM|WEP@XK`4zfg$*+$dxr|Zm;LU*&eD&%nzGV@8 zKO=B?GS}vdZ1|XJvC2j?JL8ZS%9N`{sRYCwTLjP+?i#C~;`gBd#pd8MN6Q+E6~W&i zT1A^q@wjs%fBt*jhW_*4RP9Uo4Z$KhPx&lRseHXTay?FV-!cOO%k|o0$aS~CX9X}W zyyDSgs|EZBT#9U+$ub*Lg7lpjJ)=D{=pc6(GqgqklHis%nV8lG9Sg(S?+$9({*bp! zDTDwD(_)ul`LlA4ps(;?H}HKm>OO(HXf>x6%>W?x=^7U{+8mamOwT!sYS5IGv!ll0 zvvRI5JS4y^Ww^uv^=9qj8@YI+IER&0mAXZ}q|?Y}D`L*6n5&J35U@A4GHbf%Pw}?y zWE%u|^`?9q4HY=y)o{x=jc{q51|%bbdP+Jmdmo6PGE4A3Bm)d=D6Vnfb=@gYu;d4; zMG*EhQdq<`=q@HDKCP}D&@MFtR?Zisjm8F!-89}?j5bytXLW|L^>)afVXE9+gQT=brBvT6?wAsrIRMmvpTx zEcL!25-9X6N=Txsb8YaH?)fdK=z7t)REaF{WSY(Ktn8-K4zsgDd;Fu4l`BAaM02QT z5w&zh+GcSq;(__Z*zA5JxTW!)pj?;;WknD!1BZzGvXxJ5Fd{eU(Dn4X`#IPG=apM& zh~UVWfs$WX!u~QRE3B|&vfY__aC!}^@MwLfkEU5=&C&{v$G{w&T@x9olJaMuIA$M; z@td2j0ecZPfkec}L>#HtN-AZhUZ1=ApX#{-3?gy6GH4@~HwKQ|C@Bs3%+{V+SW>EW zPzC^~xRmbRR64l)IR7ylG@!5aq=INLbiW0; z?Kb(nmt2L}3wS+LT^Aq3ngq~Mhe#a0?JWb}M}=~f%{3C;jv6gd>;qfjUws~aB~Ud1rg;XjIum_o%Bxja zI{jAzRwhu<=xxn*8_jja%8&Et&N}={tYY6W%;H2Il)ddr*Zneew)}a#q;Oql)$}Pp z};P!Q`v-#*riYZti<8^XAY#Y5jfg_i(-F7l@0E4WipA zcqYgb66G-rzg}uDmFg}HDX??1V1>~U6ZmuVhLw!N%mB~XOp6isljE~13S}KEOMIEc>QNw}M;TCI&YzNpQo$M4vouP2h&zo};HhfgC(;ko{a|f1=)jHMfk}uQ?VSW( z)L+&&;=a(QWfWMnf<4z3Qk>;9uQxIaZQ3ph_blXntvtRcYa>pne%Vrzx_4C-6sc zdZW8YP0BE2w?zg9SLG!|IF~-hN-Ae8nd`0gik=%Oaf3jWl&8-$3Zd*Sek^V%0|Fl% zQNnzodAk>6U?=81kov2o^>j&+19=O%3jw-|#zqvI3x+^0{IVlxJm+p}Ax=26khd1t zps1OtYg(CR)8B=HCB}7RJ$(wAp>d0>azX7RuyfiMNyVY2wJCVoCY3Ww;cl2jKyB_; z^zrEus>EF%aGft!aTM7UdKQXF{Q>W=LNrI`gq4<8_g8)^#_O1-RxP&uevabN+NsH%vOC-r4>g~?LhHDEu;x&$K6k%G&bUeNaBkK_k)&E_TUYU= z)r#x#B!S*aZFA*UUP{hE(lkc&K!xNv;xI2 zc)qQ-hfHOOSr}sS1IS#@m+dHXmP|6w0`V*P^}uGS?)VbDkbt_>J5b&cx?ew(BGw<40-_gM$L`0D?`tNZ9F^^@A;v({42;p6FT1>S@!-KZU z{a)#n4^xGMQ6bGC=1nepF^L2?|E!G$42A9Q0W+m}Y?#<`@6;d#ilVp*H-QA$Y_^x+ zg5OK15)<>s(@SCa=C$vezV6a}6PmW0KCt7yftV5GlFeGUCE;mV>1vwx^)ju34CYoX zU~v9om2R>%gl%;h|I%+eg=FsuX>oeiRN@4^jg!*=-PUr|7}Gndir$bA*vHBV2->sP z+fCXGLXxp~;!({hLiBPiDF(YfQ$AR$qYgtfBj}VBpJ+PIb4$P)&HAy^EF~J|em@ND zQa*A0uFY=QVF(DCp$a`s^eF>j9+mWgXA0n9zLm<)SgLP=H!B#b5^>2s$Rpz+D(jhZ z8M3wQ;@3|+VwdB327RqW1`443T`&KnXnZJs7iulEnEv&y3}r~^sr)aoKE~EGSHN^* z%|)M$r9RV_n^jLi2JrDa=UIX$5}QQ*O{(wwSWv=$u)+n@$>&4A8FO8O4w6 ziAyUxrCfcMI%K2iK5uI=7B{4{KWh3xGZp^fy_)+M|B_z#_|Fdo^uFJnA^$qD$QX!dR;hciZ#~%UQ$rI^>rN>lZ|VWU@E&ub;9F;DOijq6HJN@h zV_BQDB}?lU;QqlwFH^si`()EmuEO1`$>{0pSz-jGwoaO=z^vofK{PnY%>#&-r2gxWKh?iS~^|b zLdzOtE{s++y*RuQV94xE>4m0ib*#~PxxCPvMwtT6>G4=X#exicLBaQ&5Z>t+;JA1<)T4j|0*rlx9GB!ACO2wp0_crM_1 zs;-J5VBNmt+)V2UuKTgtAZpj~ARe1kkipRGm{?3+@FZC2xDT0~I#@q@aK+_`fny9V zXUwk2%XB&RVgk;L^f#x>Rp25eZt~p6=hSUmYlT!jZNu(^;t8*0d{Z(y$hfWm4Za=p z%vKeVMZ;83o|yWh7OX0F7|XGsOnY=$=ec&PIuY6WHN^d!w|w01Y`p-v_{ z$@b*&oUYR{Ape~nO@DwNIQuqj?leqOW^TL$v{}Xw8TU_7HQuPK!Xn?>WVbliAio7Qa%QaPtIVKVYkx(tnb`Qm$;;~5 z4lY1^Y@vM+<^{{}M)RplV=au#A>R^1Eko_>nmji4KH2J%N9L=|j1qpEZB_gRweI~6 zpw=#~rDK2=OIvkn6vtXYV#FL^32i()Ip`iM@Q^!ZJUj=KFrqy-o`fZqBR3lxw z2eOvOVoIxmSL{XfR4hWhW_H2RJoiZ7E4LuC=2oSG(t*LLYS;n?vbm|_R>|SIiKa?9 zZ56lD9{Wau)^r#k4e40zVT2OC$`Lsc6R8^%MrT=KWManftxdnKx60lz!jhzO9uBM} z>U8Y#_XzRcO|Lbp+F%di0LEaf638k+j?mD@_lrsGzgspC6vX)zcRa3q?sXz})b#T{ z{^`8uS}u9uGRfNr!TE_|=a05JWyLWf{Ua336xRWrUr%*Oibat+uEy##pIII_?)(+H zm+?lueY8$_0<69b9KU`o~@$OyW^iO@f$NUsC&~ z45F~wmh&M@ab+kdEn6n)>&R(kS{&?-chc z{6{_qI{l)2Iv^}5VM=?9Zt20K>77QgeG-IKnn1x#G2(wz`SpRA^Lq1+SPpVwMTd@D zP;Gp1P#BhaZIkH|?jJPLCz>K_G2ggFYL-ucwglj5wRvo)b=XdZZ~ZTTD`2cVVGkzd zjXx=)Shc@K!$Sq4vX51G5Qm}Gv@L%VxRMD)GsL~QhMctK<4vxy)}%xo`S$=?sTAVg zhQGRU{CXhz*e8SMVWEOzqMPjNOnorSS9#+Hzp+a>Z{s-fUZ1)h_Xti~*G!_$w<7aX z3o5<(ni!*dQ@o(}y`gNgPRI-_V6Hi%5ztEI4nLRfCG(4y^Ss;ZcMvNHwaOBz_9Gnq zODjbL?*^leFDQ6%!8mLvVb*SMW}mBQEyuCNcVo_ibwf(>r~_eClG#HBv`iL|Km;Qon#a+7Jfg=?y1FC4AHdpJl(ft~#FtHN z&05rBXnk_N*SOtsG~@D9)u5&4J0+g&`*t31Nm{lYX=qLsL{1`jUPVE!d&03wYw8$F zq})A7O4cQNWntRj=!dWtwX57!$X+G4yqb-4H}@7-j8e^k?^L1pU($FcLYKz&Z}cih zXHI%Sh-#+Fwk+@cv8`2SBr~f&woB*8U|Xd_ZNc~F<|vAyN&$K(Jc#2d&p8dI|F8(CV%>e*Lc31xJ@erYee zy#W{C=q$Aa!lR7iD5ur463A@i^Ur{)iRn<>;Nz={d4y!qkY>X4H+oyHSuDg?=CtFY z2Eyv7Mw3~bmm*us3cNP^ft&N-qGAS%nPc;lCr6%}vvo&&X7e68iWp@!^vg_gPrE2E z=Zd>**&P7@MPD}KEdX}#eVAH7sf4fzuJ50QPCuuP^nm$wQA1Plv@9t*!VA}-W;N&N zU|I{_^!5m+tOs&6o?6kI<|-rHX&$naTLlL4)UI#Nlfrp?5GsFA)%f?J@uLg%c_FxO zBb%$K#oOh&h6&QuP?%4UK`YdirL<0K+ZVQoZ@@wvadI7B?Q0)L_AM;!bUok2Rk{D! zsrX8XE)L((KS~NBC2?}D>MS13Q%_IBcrT+@NVYyW7u7&TT5s~nj*)OQ2vN-g+;Qb?ZL|{Z|PwF@H0-b z@Ri_~BduINTfFf$oHpGX&3Z<}sa#FAm=F|daGaw8|)&yES zg}^EYvjR-MlN_`z)>{P0$8(0TSETjS7aMHOG}<*c9(ss9zu%I#Mk~}6bbNd0+ep3E z6-)vF0T0VFM-sbJ;un*Bw}W4$=Z=&!H4n-A6bpH4`jO6%q19h>}CsJDLT@6%` zJJ@(z%vpM5CuHiWK6Fp#)3R85uDF_8vwFeG$AXdfR_90W_72yj5s~jx3{GMEs{@`8 z!7a@gl}E%=QmdCN4{dF}tb@wL960C>A3q#a(H&6@({SZKAX#eCAEGc^S-0Do5cYwW z&um^eoyB;N#X?p2eg|l>1q=%_*Q&CsG)_prS`N++^zVL1?2rVMKdG^EKLHuhg0o1~ zgPHHUgvEpI3&d7DR063 zoIwXNjqL_7-o9z*Y1oWTv+B3?th-UA zdN;G=5fjh{0mOhi^a=`!`h#Tq-9avzlunf8PqaV{4~=lOI|ui1Tt!uwsB;*u;lmzT z+t2xAHx?!&5o|1OuRjnzhH^Gl-dH>SzV16zd?)44p-->$&;GPb%F{Trz$MBbr7XY8moq}#$fTij%+1G2 z*lDDcA9f{H4aqf+`l`b?Q)STfXlw=zE>z77vbxFawk|%41FuAcU6Var3yn12^9fcQ zOVeno8q0AD@I>LG1(b2e(0Vg+Bt$dV<`s%_RsPQjE1gpr*o!oH>=yj`x_ZS<#uh)66g>$yHJyqfl)lz`fP6D)e*9o`nh zi5V`6i$-E4-%TKzZA<8#yR56HJhHG!6gUu#k<6xe)D^-xQ2y5nglaRn3zB$FX+b#~ zH9fl&@UW$;6Kqw*`N9;I_3cuZ$dRneI7Z{=2`KLFE`6L^*0~QneVrgg(Zu*82WDVt z(2R_&9BVZ~RXKFwG3x}O(B!%B@rOEnXR?YnXFg}R#POQZW?pBpx2=2~7uX!7xO%VP|fX$YnO&BK(t_4)x{W-$xmdw39y5ILj zJGh{3=iTB1z|_karG(1v8yz2pJ(a7<^n0EaU-iMx-2W-A$|9)dlf5G}RWWJA$lMT< zT{CEN2%o4=taSm2v@Q7hb;i%zjsbcehXp(m$nTOIMO6(PF?#8H@E~iVEUjY=9~!o% zAF!Oawmc*w))ukoTZ&PM9M@Ho^cTkF*93g(*n@e|p*iB}t+3FcXGvcbr&M-TgxNUo z(TkKm<;B)~L<$A#`rOtEfZOM$e{t2MuN-$qcap6qCZOaKNfZT55{G;i<>P86E z-=%^6g?qaH?o^TT^n`s#I4W`mg@z!()wh1mGf!7pC}*?f4ax1LFv>64Hrv3(H%E^l zJ$n+Meq9ENfP~rF;)x)pNw=h{FeTaDs<(LqcYdR$7eRpwQIRXP2EXn!dge!_43rgJ z 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

sqgs>XJ$G)Sp_QQMh}eW#RY*c~pV794tW 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

5G@a+& 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 z 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