From d933d2af22e0276cdfde5cdca93a55427b466431 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:32:58 +0100 Subject: [PATCH 01/26] [pre-commit.ci] pre-commit autoupdate (#131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 23.10.1 → 23.11.0](https://github.com/psf/black-pre-commit-mirror/compare/23.10.1...23.11.0) - [github.com/astral-sh/ruff-pre-commit: v0.1.4 → v0.1.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.4...v0.1.5) - [github.com/mscheltienne/bibclean: 0.7.1 → 0.8.0](https://github.com/mscheltienne/bibclean/compare/0.7.1...0.8.0) - [github.com/adrienverge/yamllint: v1.32.0 → v1.33.0](https://github.com/adrienverge/yamllint/compare/v1.32.0...v1.33.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 61def20e..dee9e153 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,14 +9,14 @@ repos: files: pycrostates - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black args: [--quiet] files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.5 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -36,7 +36,7 @@ repos: additional_dependencies: [tomli] - repo: https://github.com/mscheltienne/bibclean - rev: 0.7.1 + rev: 0.8.0 hooks: - id: bibclean-fix files: docs/references.bib @@ -49,7 +49,7 @@ repos: files: pyproject.toml - repo: https://github.com/adrienverge/yamllint - rev: v1.32.0 + rev: v1.33.0 hooks: - id: yamllint args: [--strict, -c, .yamllint.yaml] From aa2d5d20dea9a9d0b719fe0e73a004d97ed47b75 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:44:04 +0100 Subject: [PATCH 02/26] [pre-commit.ci] pre-commit autoupdate (#132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.5 → v0.1.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.5...v0.1.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dee9e153..014fc521 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 72a7b1217fc4bdd941a655994cb54795f80843d9 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Tue, 21 Nov 2023 16:05:54 +0100 Subject: [PATCH 03/26] add conftest to MANIFEST --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..3c3a5d71 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +exclude pycrostates/conftest.py From 6cf77b28d26b8483f8e20bc2de481ffb1f2e5550 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:55:08 +0100 Subject: [PATCH 04/26] Bump actions/setup-python from 4 to 5 (#134) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/code-style.yaml | 2 +- .github/workflows/publish.yaml | 2 +- .github/workflows/pytest.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml index 2bcb7a0a..34f12fae 100644 --- a/.github/workflows/code-style.yaml +++ b/.github/workflows/code-style.yaml @@ -16,7 +16,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' architecture: 'x64' diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index cf8a2cf7..b534e5fa 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -13,7 +13,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' architecture: 'x64' diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index f306a318..a7caa65a 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -27,7 +27,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: 'x64' @@ -74,7 +74,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: 'x64' From 62bc37ba0e9ccae38e600fac009f54a285a4e301 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:25:31 +0100 Subject: [PATCH 05/26] [pre-commit.ci] pre-commit autoupdate (#135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.12.0 → 5.13.0](https://github.com/pycqa/isort/compare/5.12.0...5.13.0) - [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.1.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.1.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 014fc521..267383c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: repos: - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.0 hooks: - id: isort files: pycrostates @@ -16,7 +16,7 @@ repos: files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 608787abca41d759580c7e30d378c3a0c5d3b42b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 10:50:49 +0100 Subject: [PATCH 06/26] [pre-commit.ci] pre-commit autoupdate (#136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.13.0 → 5.13.2](https://github.com/pycqa/isort/compare/5.13.0...5.13.2) - [github.com/psf/black-pre-commit-mirror: 23.11.0 → 23.12.0](https://github.com/psf/black-pre-commit-mirror/compare/23.11.0...23.12.0) - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 267383c0..46e20331 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,20 +3,20 @@ ci: repos: - repo: https://github.com/pycqa/isort - rev: 5.13.0 + rev: 5.13.2 hooks: - id: isort files: pycrostates - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.11.0 + rev: 23.12.0 hooks: - id: black args: [--quiet] files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From e022f59dc76981def49c04b2ef5409f8677dceee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 22:44:20 +0100 Subject: [PATCH 07/26] [pre-commit.ci] pre-commit autoupdate (#140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 23.12.0 → 23.12.1](https://github.com/psf/black-pre-commit-mirror/compare/23.12.0...23.12.1) - [github.com/astral-sh/ruff-pre-commit: v0.1.8 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.8...v0.1.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46e20331..2c415191 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,14 +9,14 @@ repos: files: pycrostates - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.12.0 + rev: 23.12.1 hooks: - id: black args: [--quiet] files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 + rev: v0.1.9 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 5558f9220006c8c54f764f739738492426ccb078 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Wed, 3 Jan 2024 12:17:26 +0100 Subject: [PATCH 08/26] Restore numpy 2.0 pip-pre pytest workflow (#141) * split pip-pre matplotlib * Revert "split pip-pre matplotlib" This reverts commit 16d60c5ee5d4b8c5d298c79bd3e1e0513521b8b3. * try pins * try again * try again --- .github/workflows/pytest.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index a7caa65a..288b80a8 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -83,7 +83,9 @@ jobs: python -m pip install --progress-bar off --upgrade pip setuptools python -m pip install --progress-bar off .[test] python -m pip install --progress-bar off --upgrade git+https://github.com/mne-tools/mne-python - python -m pip install --progress-bar off --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --timeout=180 numpy scipy scikit-learn matplotlib + python -m pip install matplotlib + python -m pip install --progress-bar off --upgrade --no-deps --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --timeout=180 matplotlib + python -m pip install --progress-bar off --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --timeout=180 numpy scipy scikit-learn - name: Display system information run: pycrostates-sys_info --developer - name: Display MNE info From 25309f7fbf7d3999a35f29380b7dffa51642dfcf Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Wed, 3 Jan 2024 13:06:07 +0100 Subject: [PATCH 09/26] Drop black in favor of ruff and generate stubs to expand docstrings in Pylance and IDEs (#137) * update style workflows * fix type-hint * update dependencies and configuration * add workflows to generate stubs * improve _docs.py * improve type-hints and ignore html templates * fix stubs * fix codespell --- .github/workflows/code-style.yaml | 49 --------------- .github/workflows/publish.yaml | 6 +- .github/workflows/stubs.yaml | 41 ++++++++++++ .pre-commit-config.yaml | 19 +++--- pycrostates/__init__.py | 2 +- pycrostates/cluster/__init__.py | 2 +- pycrostates/cluster/utils/__init__.py | 2 +- pycrostates/datasets/__init__.py | 2 +- pycrostates/io/__init__.py | 2 +- pycrostates/metrics/__init__.py | 2 +- pycrostates/preprocessing/__init__.py | 2 +- pycrostates/segmentation/__init__.py | 2 +- pycrostates/utils/__init__.py | 2 +- pycrostates/utils/_docs.py | 91 ++++++++------------------- pycrostates/utils/_imports.py | 4 +- pycrostates/utils/_logs.py | 2 +- pycrostates/viz/__init__.py | 2 +- pyproject.toml | 38 +++++------ tools/stubgen.py | 78 +++++++++++++++++++++++ 19 files changed, 189 insertions(+), 159 deletions(-) delete mode 100644 .github/workflows/code-style.yaml create mode 100644 .github/workflows/stubs.yaml create mode 100644 tools/stubgen.py diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml deleted file mode 100644 index 34f12fae..00000000 --- a/.github/workflows/code-style.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: style -concurrency: - group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} - cancel-in-progress: true -on: # yamllint disable-line rule:truthy - pull_request: - push: - branches: [main] - workflow_dispatch: - -jobs: - style: - timeout-minutes: 10 - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Setup Python 3.9 - uses: actions/setup-python@v5 - with: - python-version: '3.9' - architecture: 'x64' - - name: Install dependencies - run: | - python -m pip install --progress-bar off --upgrade pip setuptools - python -m pip install --progress-bar off .[style] - - name: Run isort - uses: isort/isort-action@master - - name: Run black - uses: psf/black@stable - with: - options: "--check --verbose" - - name: Run Ruff - run: ruff check pycrostates - - name: Run codespell - uses: codespell-project/actions-codespell@master - with: - check_filenames: true - check_hidden: true - skip: ./.git,./build,./.github,*.bib,./.mypy_cache,./.pytest_cache - ignore_words_file: ./.codespellignore - - name: Run pydocstyle - run: pydocstyle . - - name: Run bibclean - run: bibclean-check docs/references.bib - - name: Run toml-sort - run: toml-sort pyproject.toml --check - - name: Run yamllint - run: yamllint . -c .yamllint.yaml --strict diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index b534e5fa..6fcb3b14 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,7 +20,11 @@ jobs: - name: Install dependencies run: | python -m pip install --progress-bar off --upgrade pip setuptools - python -m pip install --progress-bar off .[build] + python -m pip install --progress-bar off -e .[build,stubs] + - name: Display system information + run: pycrostates-sys_info --developer + - name: Generate stub files + run: python tools/stubgen.py - name: Build and publish env: TWINE_USERNAME: __token__ diff --git a/.github/workflows/stubs.yaml b/.github/workflows/stubs.yaml new file mode 100644 index 00000000..90545bf2 --- /dev/null +++ b/.github/workflows/stubs.yaml @@ -0,0 +1,41 @@ +name: stubs +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} + cancel-in-progress: true +on: # yamllint disable-line rule:truthy + schedule: + - cron: '0 3 * * *' + workflow_dispatch: + +jobs: + generate: + timeout-minutes: 10 + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: 3.9 + architecture: 'x64' + - name: Install package + run: | + python -m pip install --progress-bar off --upgrade pip setuptools + python -m pip install --progress-bar off -e .[stubs] + - name: Display system information + run: pycrostates-sys_info --developer + - name: Generate stub files + run: python tools/stubgen.py + - name: Push stub files + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + if [ -n "$(git status --porcelain)" ]; then + git add . + git commit -m "deploy stub files [ci skip]" + git push + fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c415191..5bfc1b1b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,3 @@ -ci: - skip: [codespell, pydocstyle, yamllint] - repos: - repo: https://github.com/pycqa/isort rev: 5.13.2 @@ -8,25 +5,23 @@ repos: - id: isort files: pycrostates - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.12.1 - hooks: - - id: black - args: [--quiet] - files: pycrostates - - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.9 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix] + name: ruff linter + args: [--fix] + files: pycrostates + - id: ruff-format + name: ruff formatter files: pycrostates - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: - id: codespell - args: [--check-filenames, --ignore-words=.codespellignore, --skip=*.bib] + args: [--write-changes] + additional_dependencies: [tomli] - repo: https://github.com/pycqa/pydocstyle rev: 6.3.0 diff --git a/pycrostates/__init__.py b/pycrostates/__init__.py index 999f2576..950b6a86 100644 --- a/pycrostates/__init__.py +++ b/pycrostates/__init__.py @@ -5,7 +5,7 @@ from .utils._logs import set_log_level from .utils.sys_info import sys_info # noqa: F401 -__all__ = ( +__all__: tuple[str, ...] = ( "cluster", "datasets", "metrics", diff --git a/pycrostates/cluster/__init__.py b/pycrostates/cluster/__init__.py index bd97d04f..4d6c6ca3 100644 --- a/pycrostates/cluster/__init__.py +++ b/pycrostates/cluster/__init__.py @@ -18,7 +18,7 @@ from .aahc import AAHCluster # noqa: F401 from .kmeans import ModKMeans # noqa: F401 -__all__ = ( +__all__: tuple[str, ...] = ( "ModKMeans", "AAHCluster", ) diff --git a/pycrostates/cluster/utils/__init__.py b/pycrostates/cluster/utils/__init__.py index d224258a..235bf2e1 100644 --- a/pycrostates/cluster/utils/__init__.py +++ b/pycrostates/cluster/utils/__init__.py @@ -2,4 +2,4 @@ from .utils import optimize_order # noqa: F401 -__all__ = ("optimize_order",) +__all__: tuple[str, ...] = ("optimize_order",) diff --git a/pycrostates/datasets/__init__.py b/pycrostates/datasets/__init__.py index 77d82eea..77397cab 100644 --- a/pycrostates/datasets/__init__.py +++ b/pycrostates/datasets/__init__.py @@ -7,4 +7,4 @@ from . import lemon -__all__ = ("lemon",) +__all__: tuple[str, ...] = ("lemon",) diff --git a/pycrostates/io/__init__.py b/pycrostates/io/__init__.py index 6bff703e..01096d4c 100644 --- a/pycrostates/io/__init__.py +++ b/pycrostates/io/__init__.py @@ -4,4 +4,4 @@ from .meas_info import ChInfo from .reader import read_cluster -__all__ = ("ChData", "ChInfo", "read_cluster") +__all__: tuple[str, ...] = ("ChData", "ChInfo", "read_cluster") diff --git a/pycrostates/metrics/__init__.py b/pycrostates/metrics/__init__.py index 926e35ec..073f9075 100644 --- a/pycrostates/metrics/__init__.py +++ b/pycrostates/metrics/__init__.py @@ -7,7 +7,7 @@ from .dunn import dunn_score from .silhouette import silhouette_score -__all__ = ( +__all__: tuple[str, ...] = ( "calinski_harabasz_score", "davies_bouldin_score", "dunn_score", diff --git a/pycrostates/preprocessing/__init__.py b/pycrostates/preprocessing/__init__.py index 88aea68e..6d29671d 100644 --- a/pycrostates/preprocessing/__init__.py +++ b/pycrostates/preprocessing/__init__.py @@ -7,4 +7,4 @@ from .resample import resample from .spatial_filter import apply_spatial_filter -__all__ = ("apply_spatial_filter", "extract_gfp_peaks", "resample") +__all__: tuple[str, ...] = ("apply_spatial_filter", "extract_gfp_peaks", "resample") diff --git a/pycrostates/segmentation/__init__.py b/pycrostates/segmentation/__init__.py index 85714ecc..17b0c492 100644 --- a/pycrostates/segmentation/__init__.py +++ b/pycrostates/segmentation/__init__.py @@ -4,7 +4,7 @@ compute_transition_matrix, ) -__all__ = ( +__all__: tuple[str, ...] = ( "compute_expected_transition_matrix", "compute_transition_matrix", "EpochsSegmentation", diff --git a/pycrostates/utils/__init__.py b/pycrostates/utils/__init__.py index 9ab02015..8877fae1 100644 --- a/pycrostates/utils/__init__.py +++ b/pycrostates/utils/__init__.py @@ -3,4 +3,4 @@ from ._config import get_config from .utils import _compare_infos, _corr_vectors, _distance_matrix # noqa: F401 -__all__ = ("get_config",) +__all__: tuple[str, ...] = ("get_config",) diff --git a/pycrostates/utils/_docs.py b/pycrostates/utils/_docs.py index 7e080d96..3aa9432a 100644 --- a/pycrostates/utils/_docs.py +++ b/pycrostates/utils/_docs.py @@ -23,16 +23,15 @@ ) for key in keys: - entry = docdict_mne[key] + entry: str = docdict_mne[key] if ".. versionchanged::" in entry: entry = entry.replace(".. versionchanged::", ".. versionchanged:: MNE ") if ".. versionadded::" in entry: entry = entry.replace(".. versionadded::", ".. versionadded:: MNE ") docdict[key] = entry +del key -docdict[ - "verbose" -] = """ +docdict["verbose"] = """ verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the @@ -40,27 +39,19 @@ ``"WARNING"`` for False and to ``"INFO"`` for True.""" # ---- Clusters ---- -docdict[ - "n_clusters" -] = """ +docdict["n_clusters"] = """ n_clusters : int The number of clusters, i.e. the number of microstates. """ -docdict[ - "cluster_centers" -] = """ +docdict["cluster_centers"] = """ cluster_centers : array (n_clusters, n_channels) Fitted clusters, i.e. the microstates maps.""" -docdict[ - "cluster_names" -] = """ +docdict["cluster_names"] = """ cluster_names : list | None Name of the clusters.""" # ---- Metrics ----- -docdict[ - "cluster" -] = """ +docdict["cluster"] = """ cluster : :ref:`cluster` Fitted clustering algorithm from which to compute score. For more details about current clustering implementations, check the :ref:`Clustering` section of the @@ -68,42 +59,28 @@ """ # ------ I/O ------- -docdict[ - "fname_fiff" -] = """ +docdict["fname_fiff"] = """ fname : str | Path Path to the ``.fif`` file where the clustering solution is saved.""" # -- Segmentation -- -docdict[ - "cluster_centers_seg" -] = """ +docdict["cluster_centers_seg"] = """ cluster_centers : array (n_clusters, n_channels) Clusters, i.e. the microstates maps used to compute the segmentation.""" -docdict[ - "labels_raw" -] = """ +docdict["labels_raw"] = """ labels : array of shape ``(n_samples,)`` Microstates labels attributed to each sample, i.e. the segmentation.""" -docdict[ - "labels_epo" -] = """ +docdict["labels_epo"] = """ labels : array of shape ``(n_epochs, n_samples)`` Microstates labels attributed to each sample, i.e. the segmentation.""" -docdict[ - "labels_transition" -] = """ +docdict["labels_transition"] = """ labels : array of shape ``(n_samples,)`` or ``(n_epochs, n_samples)`` Microstates labels attributed to each sample, i.e. the segmentation.""" # TODO: predict_parameters docstring is missing. -docdict[ - "predict_parameters" -] = """ +docdict["predict_parameters"] = """ predict_parameters : dict | None The prediction parameters.""" -docdict[ - "stat_transition" -] = """ +docdict["stat_transition"] = """ stat : str Aggregate statistic to compute transitions. Can be: @@ -112,9 +89,7 @@ the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is always equal to ``100``.""" -docdict[ - "stat_expected_transitions" -] = """ +docdict["stat_expected_transitions"] = """ stat : str Aggregate statistic to compute transitions. Can be: @@ -122,48 +97,34 @@ the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is always equal to ``100``.""" -docdict[ - "ignore_repetitions" -] = """ +docdict["ignore_repetitions"] = """ ignore_repetitions : bool If ``True``, ignores state repetitions. For example, the input sequence ``AAABBCCD`` will be transformed into ``ABCD`` before any calculation. This is equivalent to setting the duration of all states to 1 sample.""" -docdict[ - "transition_matrix" -] = """ +docdict["transition_matrix"] = """ T : array of shape ``(n_cluster, n_cluster)`` Array of transition probability values from one label to another. First axis indicates state ``"from"``. Second axis indicates state ``"to"``.""" # ------ Viz ------- -docdict[ - "cmap" -] = """ +docdict["cmap"] = """ cmap : str | colormap | None The colormap to use. If None, ``viridis`` is used.""" -docdict[ - "block" -] = """ +docdict["block"] = """ block : bool Whether to halt program execution until the figure is closed.""" -docdict[ - "axes_topo" -] = """ +docdict["axes_topo"] = """ axes : Axes | None Either ``None`` to create a new figure or axes (or an array of axes) on which the topographic map should be plotted. If the number of microstates maps to plot is ``≥ 1``, an array of axes of size ``n_clusters`` should be provided.""" -docdict[ - "axes_seg" -] = """ +docdict["axes_seg"] = """ axes : Axes | None Either ``None`` to create a new figure or axes on which the segmentation is plotted.""" -docdict[ - "axes_cbar" -] = """ +docdict["axes_cbar"] = """ cbar_axes : Axes | None Axes on which to draw the colorbar, otherwise the colormap takes space from the main axes.""" @@ -218,16 +179,16 @@ def fill_doc(f: Callable) -> Callable: def _indentcount_lines(lines: list[str]) -> int: """Minimum indent for all lines in line list. - >>> lines = [' one', ' two', ' three'] + >>> lines = [" one", " two", " three"] >>> indentcount_lines(lines) 1 >>> lines = [] >>> indentcount_lines(lines) 0 - >>> lines = [' one'] + >>> lines = [" one"] >>> indentcount_lines(lines) 1 - >>> indentcount_lines([' ']) + >>> indentcount_lines([" "]) 0 """ indent = sys.maxsize @@ -270,7 +231,7 @@ def copy_doc(source: Callable) -> Callable: >>> class B(A): ... @copy_doc(A.m1) ... def m1(): - ... ''' this gets appended''' + ... '''this gets appended''' ... pass >>> print(B.m1.__doc__) Docstring for m1 this gets appended diff --git a/pycrostates/utils/_imports.py b/pycrostates/utils/_imports.py index a60ce880..33fabb9e 100644 --- a/pycrostates/utils/_imports.py +++ b/pycrostates/utils/_imports.py @@ -9,7 +9,7 @@ # A mapping from import name to package name (on PyPI) when the package name # is different. {python: PyPI} -INSTALL_MAPPING = {} +_INSTALL_MAPPING: dict[str, str] = {} def import_optional_dependency(name: str, extra: str = "", raise_error: bool = True): @@ -38,7 +38,7 @@ def import_optional_dependency(name: str, extra: str = "", raise_error: bool = T The imported module when found. None is returned when the package is not found and raise_error is False. """ - package_name = INSTALL_MAPPING.get(name) + package_name = _INSTALL_MAPPING.get(name) install_name = package_name if package_name is not None else name try: diff --git a/pycrostates/utils/_logs.py b/pycrostates/utils/_logs.py index 6b74f670..42f401aa 100644 --- a/pycrostates/utils/_logs.py +++ b/pycrostates/utils/_logs.py @@ -158,7 +158,7 @@ class _use_log_level: %(verbose)s """ - def __init__(self, verbose: Union[bool, str, int, None] = None): + def __init__(self, verbose: Optional[Union[bool, str, int]] = None): self._old_level = logger.level self._level = verbose diff --git a/pycrostates/viz/__init__.py b/pycrostates/viz/__init__.py index 5396d3ca..17c6fe4e 100644 --- a/pycrostates/viz/__init__.py +++ b/pycrostates/viz/__init__.py @@ -4,7 +4,7 @@ from .cluster_centers import plot_cluster_centers from .segmentation import plot_epoch_segmentation, plot_raw_segmentation -__all__ = ( +__all__: tuple[str, ...] = ( "plot_cluster_centers", "plot_raw_segmentation", "plot_epoch_segmentation", diff --git a/pyproject.toml b/pyproject.toml index 142b5fc5..55584f50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,20 +75,24 @@ docs = [ 'sphinx_gallery', 'sphinxcontrib-bibtex', ] +stubs = [ + 'isort', + 'mypy', + 'ruff>=0.1.8', +] style = [ 'bibclean', - 'black', - 'codespell', + 'codespell[toml]>=2.2.4', 'isort', 'pydocstyle[toml]', - 'ruff', + 'ruff>=0.1.8', 'toml-sort', 'yamllint', ] test = [ 'pymatreader', - 'pytest', 'pytest-cov', + 'pytest>=6.0', ] [project.scripts] @@ -100,21 +104,11 @@ homepage = 'https://pycrostates.readthedocs.io/en/master/' source = 'https://github.com/vferat/pycrostates' tracker = 'https://github.com/vferat/pycrostates/issues' -[tool.black] -extend-exclude = ''' -( - __pycache__ - | \.github/ - | docs/ - | paper/ - | pycrostates/html_templates/repr - | setup.py - | tutorials/ -) -''' -include = '\.pyi?$' -line-length = 88 -target-version = ['py39'] +[tool.codespell] +check-filenames = true +check-hidden = true +ignore-words = '.codespellignore' +skip = 'build,.git,.mypy_cache,.pytest_cache,paper/*' [tool.coverage.report] exclude_lines = [ @@ -166,8 +160,14 @@ extend-exclude = [ 'setup.py', ] line-length = 88 +select = ["E", "F", "W"] +target-version = 'py39' + +[tool.ruff.format] +docstring-code-format = true [tool.ruff.per-file-ignores] +'*.pyi' = ['E501'] '__init__.py' = ['F401'] [tool.setuptools] diff --git a/tools/stubgen.py b/tools/stubgen.py new file mode 100644 index 00000000..a5d3bf09 --- /dev/null +++ b/tools/stubgen.py @@ -0,0 +1,78 @@ +import ast +import subprocess +import sys +from importlib import import_module +from pathlib import Path + +import isort +from mypy import stubgen + +import pycrostates + +directory = Path(pycrostates.__file__).parent +# remove existing stub files +for file in directory.rglob("*.pyi"): + file.unlink() +# generate stub files, including private members and docstrings +files = [ + str(file.as_posix()) + for file in directory.rglob("*.py") + if file.parent.name not in ("commands", "html_templates", "tests") + and file.name not in ("conftest.py", "_tests.py", "_version.py") +] +stubgen.main( + [ + "--no-analysis", + "--no-import", + "--include-private", + "--include-docstrings", + "--output", + str(directory.parent), + *files, + ] +) +stubs = list(directory.rglob("*.pyi")) +config = str((directory.parent / "pyproject.toml")) +config_isort = isort.settings.Config(config) + +# expand docstrings and inject into stub files +for stub in stubs: + module_path = str(stub.relative_to(directory).with_suffix("").as_posix()) + module = import_module(f"{directory.name}.{module_path.replace('/', '.')}") + module_ast = ast.parse(stub.read_text(encoding="utf-8")) + objects = [ + node + for node in module_ast.body + if isinstance(node, (ast.ClassDef, ast.FunctionDef)) + ] + for node in objects: + docstring = getattr(module, node.name).__doc__ + if not docstring and isinstance(node, ast.FunctionDef): + continue + elif docstring: + try: + node.body[0].value.value = docstring + except AttributeError: + continue + for method in node.body: + if not isinstance(method, ast.FunctionDef): + continue + docstring = getattr(getattr(module, node.name), method.name).__doc__ + if docstring: + try: + method.body[0].value.value = docstring + except AttributeError: + continue + unparsed = ast.unparse(module_ast) + # remove unused imports conflicting with arguments, kwargs, method names, ... + unparsed = unparsed.replace(", verbose as verbose", "") + unparsed = unparsed.replace( + "from ..viz import plot_cluster_centers as plot_cluster_centers", "" + ) + stub.write_text(unparsed, encoding="utf-8") + # sort imports + isort.file(stub, config=config_isort) + +# run ruff to improve stub style +exec = subprocess.run(["ruff", "format", str(directory), "--config", config]) +sys.exit(exec.returncode) From b13b777ab96af5dd1ef67077f93600964dcdbd64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Jan 2024 12:38:08 +0000 Subject: [PATCH 10/26] deploy stub files [ci skip] --- pycrostates/__init__.pyi | 11 + pycrostates/_typing.pyi | 20 + pycrostates/cluster/__init__.pyi | 5 + pycrostates/cluster/_base.pyi | 455 ++++++++++++++++++ pycrostates/cluster/aahc.pyi | 150 ++++++ pycrostates/cluster/kmeans.pyi | 218 +++++++++ pycrostates/cluster/utils/__init__.pyi | 3 + pycrostates/cluster/utils/utils.pyi | 31 ++ pycrostates/datasets/__init__.pyi | 3 + pycrostates/datasets/lemon/__init__.pyi | 6 + pycrostates/datasets/lemon/lemon.pyi | 71 +++ pycrostates/io/__init__.pyi | 5 + pycrostates/io/ch_data.pyi | 128 +++++ pycrostates/io/fiff.pyi | 125 +++++ pycrostates/io/meas_info.pyi | 163 +++++++ pycrostates/io/reader.pyi | 20 + pycrostates/metrics/__init__.pyi | 6 + pycrostates/metrics/calinski_harabasz.pyi | 32 ++ pycrostates/metrics/davies_bouldin.pyi | 52 ++ pycrostates/metrics/dunn.pyi | 49 ++ pycrostates/metrics/silhouette.pyi | 34 ++ pycrostates/preprocessing/__init__.pyi | 5 + .../preprocessing/extract_gfp_peaks.pyi | 99 ++++ pycrostates/preprocessing/resample.pyi | 91 ++++ pycrostates/preprocessing/spatial_filter.pyi | 93 ++++ pycrostates/segmentation/__init__.pyi | 8 + pycrostates/segmentation/_base.pyi | 217 +++++++++ pycrostates/segmentation/segmentation.pyi | 133 +++++ pycrostates/segmentation/transitions.pyi | 106 ++++ pycrostates/utils/__init__.pyi | 6 + pycrostates/utils/_checks.pyi | 118 +++++ pycrostates/utils/_config.pyi | 38 ++ pycrostates/utils/_docs.pyi | 70 +++ pycrostates/utils/_fixes.pyi | 8 + pycrostates/utils/_imports.pyi | 30 ++ pycrostates/utils/_logs.pyi | 119 +++++ pycrostates/utils/mixin.pyi | 16 + pycrostates/utils/sys_info.pyi | 22 + pycrostates/utils/utils.pyi | 33 ++ pycrostates/viz/__init__.pyi | 5 + pycrostates/viz/cluster_centers.pyi | 60 +++ pycrostates/viz/segmentation.pyi | 138 ++++++ 42 files changed, 3002 insertions(+) create mode 100644 pycrostates/__init__.pyi create mode 100644 pycrostates/_typing.pyi create mode 100644 pycrostates/cluster/__init__.pyi create mode 100644 pycrostates/cluster/_base.pyi create mode 100644 pycrostates/cluster/aahc.pyi create mode 100644 pycrostates/cluster/kmeans.pyi create mode 100644 pycrostates/cluster/utils/__init__.pyi create mode 100644 pycrostates/cluster/utils/utils.pyi create mode 100644 pycrostates/datasets/__init__.pyi create mode 100644 pycrostates/datasets/lemon/__init__.pyi create mode 100644 pycrostates/datasets/lemon/lemon.pyi create mode 100644 pycrostates/io/__init__.pyi create mode 100644 pycrostates/io/ch_data.pyi create mode 100644 pycrostates/io/fiff.pyi create mode 100644 pycrostates/io/meas_info.pyi create mode 100644 pycrostates/io/reader.pyi create mode 100644 pycrostates/metrics/__init__.pyi create mode 100644 pycrostates/metrics/calinski_harabasz.pyi create mode 100644 pycrostates/metrics/davies_bouldin.pyi create mode 100644 pycrostates/metrics/dunn.pyi create mode 100644 pycrostates/metrics/silhouette.pyi create mode 100644 pycrostates/preprocessing/__init__.pyi create mode 100644 pycrostates/preprocessing/extract_gfp_peaks.pyi create mode 100644 pycrostates/preprocessing/resample.pyi create mode 100644 pycrostates/preprocessing/spatial_filter.pyi create mode 100644 pycrostates/segmentation/__init__.pyi create mode 100644 pycrostates/segmentation/_base.pyi create mode 100644 pycrostates/segmentation/segmentation.pyi create mode 100644 pycrostates/segmentation/transitions.pyi create mode 100644 pycrostates/utils/__init__.pyi create mode 100644 pycrostates/utils/_checks.pyi create mode 100644 pycrostates/utils/_config.pyi create mode 100644 pycrostates/utils/_docs.pyi create mode 100644 pycrostates/utils/_fixes.pyi create mode 100644 pycrostates/utils/_imports.pyi create mode 100644 pycrostates/utils/_logs.pyi create mode 100644 pycrostates/utils/mixin.pyi create mode 100644 pycrostates/utils/sys_info.pyi create mode 100644 pycrostates/utils/utils.pyi create mode 100644 pycrostates/viz/__init__.pyi create mode 100644 pycrostates/viz/cluster_centers.pyi create mode 100644 pycrostates/viz/segmentation.pyi diff --git a/pycrostates/__init__.pyi b/pycrostates/__init__.pyi new file mode 100644 index 00000000..4f127561 --- /dev/null +++ b/pycrostates/__init__.pyi @@ -0,0 +1,11 @@ +from . import cluster as cluster +from . import datasets as datasets +from . import metrics as metrics +from . import preprocessing as preprocessing +from . import utils as utils +from . import viz as viz +from ._version import __version__ as __version__ +from .utils._logs import set_log_level as set_log_level +from .utils.sys_info import sys_info as sys_info + +__all__: tuple[str, ...] diff --git a/pycrostates/_typing.pyi b/pycrostates/_typing.pyi new file mode 100644 index 00000000..7417a49c --- /dev/null +++ b/pycrostates/_typing.pyi @@ -0,0 +1,20 @@ +from abc import ABC +from typing import Optional, Union + +from numpy.random import Generator, RandomState +from numpy.typing import NDArray + +class CHData(ABC): + """Typing for CHData.""" + +class CHInfo(ABC): + """Typing for CHInfo.""" + +class Cluster(ABC): + """Typing for a clustering class.""" + +class Segmentation(ABC): + """Typing for a clustering class.""" + +RANDomState = Optional[Union[int, RandomState, Generator]] +Picks = Optional[Union[str, NDArray[int]]] diff --git a/pycrostates/cluster/__init__.pyi b/pycrostates/cluster/__init__.pyi new file mode 100644 index 00000000..fb13d124 --- /dev/null +++ b/pycrostates/cluster/__init__.pyi @@ -0,0 +1,5 @@ +from . import utils as utils +from .aahc import AAHCluster as AAHCluster +from .kmeans import ModKMeans as ModKMeans + +__all__: tuple[str, ...] diff --git a/pycrostates/cluster/_base.pyi b/pycrostates/cluster/_base.pyi new file mode 100644 index 00000000..ee4bf7f0 --- /dev/null +++ b/pycrostates/cluster/_base.pyi @@ -0,0 +1,455 @@ +from abc import abstractmethod +from pathlib import Path as Path +from typing import Any, Optional, Union + +from _typeshed import Incomplete +from matplotlib.axes import Axes as Axes +from mne import BaseEpochs +from mne.io import BaseRaw +from numpy.typing import NDArray + +from .._typing import CHData as CHData +from .._typing import Cluster as Cluster +from .._typing import Picks as Picks +from ..segmentation import EpochsSegmentation as EpochsSegmentation +from ..segmentation import RawSegmentation as RawSegmentation +from ..utils import _corr_vectors as _corr_vectors +from ..utils._checks import _check_picks_uniqueness as _check_picks_uniqueness +from ..utils._checks import _check_reject_by_annotation as _check_reject_by_annotation +from ..utils._checks import _check_tmin_tmax as _check_tmin_tmax +from ..utils._checks import _check_type as _check_type +from ..utils._checks import _check_value as _check_value +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger +from ..utils.mixin import ChannelsMixin as ChannelsMixin +from ..utils.mixin import ContainsMixin as ContainsMixin +from ..utils.mixin import MontageMixin as MontageMixin +from .utils import optimize_order as optimize_order + +class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): + """Base Class for Microstates Clustering algorithms.""" + + _n_clusters: Incomplete + _cluster_names: Incomplete + _cluster_centers_: Incomplete + _ignore_polarity: Incomplete + _info: Incomplete + _fitted_data: Incomplete + _labels_: Incomplete + _fitted: bool + + @abstractmethod + def __init__(self): ... + def __repr__(self) -> str: + """String representation.""" + + def _repr_html_(self, caption: Incomplete | None = None): + """HTML representation.""" + + def __eq__(self, other: Any) -> bool: + """Equality == method.""" + + def __ne__(self, other: Any) -> bool: + """Different != method.""" + + def copy(self, deep: bool = True): + """Return a copy of the instance. + + Parameters + ---------- + deep : bool + If True, `~copy.deepcopy` is used instead of `~copy.copy`. + """ + + def _check_fit(self) -> None: + """Check if the cluster is fitted.""" + + def _check_unfitted(self) -> None: + """Check if the cluster is unfitted.""" + + @abstractmethod + def fit( + self, + inst: Union[BaseRaw, BaseEpochs, CHData], + picks: Picks = "eeg", + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + reject_by_annotation: bool = True, + *, + verbose: Optional[str] = None, + ) -> NDArray[float]: + """Compute cluster centers. + + Parameters + ---------- + inst : Raw | Epochs | ChData + MNE `~mne.io.Raw`, `~mne.Epochs` or `~pycrostates.io.ChData` object + from which to extract :term:`cluster centers`. + picks : str | list | slice | None + Channels to include. Note that all channels selected must have the same + type. Slices and lists of integers will be interpreted as channel indices. + In lists, channel name strings (e.g. ``['Fp1', 'Fp2']``) will pick the given + channels. Can also be the string values ``“all”`` to pick all channels, or + ``“data”`` to pick data channels. ``"eeg"`` (default) will pick all eeg + channels. Note that channels in ``info['bads']`` will be included if their + names or indices are explicitly provided. + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. + + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + """ + + def rename_clusters( + self, + mapping: Optional[dict[str, str]] = None, + new_names: Optional[Union[list[str], tuple[str, ...]]] = None, + ) -> None: + """Rename the clusters. + + Parameters + ---------- + mapping : dict + Mapping from the old names to the new names. The keys are the old names and + the values are the new names. + new_names : list | tuple + 1D iterable containing the new cluster names. The length of the iterable + should match the number of clusters. + + Notes + ----- + Operates in-place. + """ + + def reorder_clusters( + self, + mapping: Optional[dict[int, int]] = None, + order: Optional[Union[list[int], tuple[int, ...], NDArray[int]]] = None, + template: Optional[Cluster] = None, + ) -> None: + """ + Reorder the clusters of the fitted model. + + Specify one of the following arguments to change the current order: + + * ``mapping``: a dictionary that maps old cluster positions to new positions, + * ``order``: a 1D iterable containing the new order, + * ``template``: a fitted clustering algorithm used as a reference to match the + order. + + Only one argument can be set at a time. + + Parameters + ---------- + mapping : dict + Mapping from the old order to the new order. + key: old position, value: new position. + order : list of int | tuple of int | array of int + 1D iterable containing the new order. Positions are 0-indexed. + template : :ref:`cluster` + Fitted clustering algorithm use as template for ordering optimization. For + more details about the current implementation, check the + :func:`pycrostates.cluster.utils.optimize_order` documentation. + + Notes + ----- + Operates in-place. + """ + + def invert_polarity( + self, invert: Union[bool, list[bool], tuple[bool, ...], NDArray[bool]] + ) -> None: + """Invert map polarities. + + Parameters + ---------- + invert : bool | list of bool | array of bool + List of bool of length ``n_clusters``. + True will invert map polarity, while False will have no effect. + If a `bool` is provided, it is applied to all maps. + + Notes + ----- + Operates in-place. + + Inverting polarities has no effect on the other steps of the analysis as + polarity is ignored in the current methodology. This function is only used for + tuning visualization (i.e. for visual inspection and/or to generate figure for + an article). + """ + + def plot( + self, + axes: Optional[Union[Axes, NDArray[Axes]]] = None, + show_gradient: Optional[bool] = False, + gradient_kwargs: dict[str, Any] = { + "color": "black", + "linestyle": "-", + "marker": "P", + }, + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, + ): + """ + Plot cluster centers as topographic maps. + + Parameters + ---------- + axes : Axes | None + Either ``None`` to create a new figure or axes (or an array of axes) on which the + topographic map should be plotted. If the number of microstates maps to plot is + ``≥ 1``, an array of axes of size ``n_clusters`` should be provided. + show_gradient : bool + If True, plot a line between channel locations with highest and lowest + values. + gradient_kwargs : dict + Additional keyword arguments passed to :meth:`matplotlib.axes.Axes.plot` to + plot gradient line. + block : bool + Whether to halt program execution until the figure is closed. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + **kwargs + Additional keyword arguments are passed to :func:`mne.viz.plot_topomap`. + + Returns + ------- + f : Figure + Matplotlib figure containing the topographic plots. + """ + + @abstractmethod + def save(self, fname: Union[str, Path]): + """Save clustering solution to disk. + + Parameters + ---------- + fname : path-like + Path to the ``.fif`` file where the clustering solution is saved. + """ + + def predict( + self, + inst: Union[BaseRaw, BaseEpochs], + picks: Picks = None, + factor: int = 0, + half_window_size: int = 1, + tol: Union[int, float] = 1e-05, + min_segment_length: int = 0, + reject_edges: bool = True, + reject_by_annotation: bool = True, + *, + verbose: Optional[str] = None, + ): + """Segment `~mne.io.Raw` or `~mne.Epochs` into microstate sequence. + + Segment instance into microstate sequence using the segmentation smoothing + algorithm\\ :footcite:p:`Marqui1995`. + + Parameters + ---------- + inst : Raw | Epochs + MNE `~mne.io.Raw` or `~mne.Epochs` object containing the data to use for + prediction. + picks : str | list | slice | None + Channels to include. Note that all channels selected must have the same + type. Slices and lists of integers will be interpreted as channel indices. + In lists, channel name strings (e.g. ``['Fp1', 'Fp2']``) will pick the given + channels. Can also be the string values ``“all”`` to pick all channels, or + ``“data”`` to pick data channels. ``None`` (default) will pick all channels + used during fitting (e.g., ``self.info['ch_names']``). Note that channels in + ``info['bads']`` will be included if their names or indices are explicitly + provided. + factor : int + Factor used for label smoothing. ``0`` means no smoothing. Default to 0. + half_window_size : int + Number of samples used for the half window size while smoothing labels. The + half window size is defined as ``window_size = 2 * half_window_size + 1``. + It has no effect if ``factor=0`` (default). Default to 1. + tol : float + Convergence tolerance. + min_segment_length : int + Minimum segment length (in samples). If a segment is shorter than this + value, it will be recursively reasigned to neighbouring segments based on + absolute spatial correlation. + reject_edges : bool + If ``True``, set first and last segments to unlabeled. + reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. + + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + segmentation : RawSegmentation | EpochsSegmentation + Microstate sequence derivated from instance data. Timepoints are labeled + according to cluster centers number: ``0`` for the first center, ``1`` for + the second, etc.. ``-1`` is used for unlabeled time points. + + References + ---------- + .. footbibliography:: + """ + + def _predict_raw( + self, + raw: BaseRaw, + picks_data: NDArray[int], + factor: int, + tol: Union[int, float], + half_window_size: int, + min_segment_length: int, + reject_edges: bool, + reject_by_annotation: bool, + ) -> RawSegmentation: + """Create segmentation for raw.""" + + def _predict_epochs( + self, + epochs: BaseEpochs, + picks_data: NDArray[int], + factor: int, + tol: Union[int, float], + half_window_size: int, + min_segment_length: int, + reject_edges: bool, + ) -> EpochsSegmentation: + """Create segmentation for epochs.""" + + @staticmethod + def _segment( + data: NDArray[float], + states: NDArray[float], + factor: int, + tol: Union[int, float], + half_window_size: int, + ) -> NDArray[int]: + """Create segmentation. Must operate on a copy of states.""" + + @staticmethod + def _smooth_segmentation( + data: NDArray[float], + states: NDArray[float], + labels: NDArray[int], + factor: int, + tol: Union[int, float], + half_window_size: int, + ) -> NDArray[int]: + """Apply smoothing. + + Adapted from [1]. + + References + ---------- + .. [1] R. D. Pascual-Marqui, C. M. Michel and D. Lehmann. + Segmentation of brain electrical activity into microstates: + model estimation and validation. + IEEE Transactions on Biomedical Engineering, + vol. 42, no. 7, pp. 658-665, July 1995, + https://doi.org/10.1109/10.391164. + """ + + @staticmethod + def _reject_short_segments( + segmentation: NDArray[int], data: NDArray[float], min_segment_length: int + ) -> NDArray[int]: + """Reject segments that are too short. + + Reject segments that are too short by replacing the labels with the adjacent + labels based on data correlation. + """ + + @staticmethod + def _reject_edge_segments(segmentation: NDArray[int]) -> NDArray[int]: + """Set the first and last segment as unlabeled (0).""" + + @property + def n_clusters(self) -> int: + """Number of clusters (number of microstates). + + :type: `int` + """ + + @property + def info(self): + """Info instance with the channel information used to fit the instance. + + :type: `~pycrostates.io.ChInfo` + """ + + @property + def fitted(self) -> bool: + """Fitted state. + + :type: `bool` + """ + + @fitted.setter + def fitted(self, fitted) -> None: + """Fitted state. + + :type: `bool` + """ + + @property + def cluster_centers_(self) -> NDArray[float]: + """Fitted clusters (the microstates maps). + + Returns None if cluster algorithm has not been fitted. + + :type: `~numpy.array` of shape (n_clusters, n_channels) | None + """ + + @property + def fitted_data(self) -> NDArray[float]: + """Data array used to fit the clustering algorithm. + + :type: `~numpy.array` of shape (n_channels, n_samples) | None + """ + + @property + def labels_(self) -> NDArray[int]: + """Microstate label attributed to each sample of the fitted data. + + :type: `~numpy.array` of shape (n_samples, ) | None + """ + + @property + def cluster_names(self) -> list[str]: + """Name of the clusters. + + :type: `list` + """ + + @cluster_names.setter + def cluster_names(self, other: Any): + """Name of the clusters. + + :type: `list` + """ + + @staticmethod + def _check_n_clusters(n_clusters: int) -> int: + """Check that the number of clusters is a positive integer.""" diff --git a/pycrostates/cluster/aahc.pyi b/pycrostates/cluster/aahc.pyi new file mode 100644 index 00000000..72f416f0 --- /dev/null +++ b/pycrostates/cluster/aahc.pyi @@ -0,0 +1,150 @@ +from pathlib import Path as Path +from typing import Any, Optional, Union + +from _typeshed import Incomplete +from mne import BaseEpochs as BaseEpochs +from mne.io import BaseRaw as BaseRaw +from numpy.typing import NDArray + +from .._typing import Picks as Picks +from ..utils import _corr_vectors as _corr_vectors +from ..utils._checks import _check_type as _check_type +from ..utils._docs import copy_doc as copy_doc +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger +from ._base import _BaseCluster as _BaseCluster + +class AAHCluster(_BaseCluster): + """Atomize and Agglomerate Hierarchical Clustering (AAHC) algorithm. + + See :footcite:t:`Murray2008` for additional information. + + Parameters + ---------- + n_clusters : int + The number of clusters, i.e. the number of microstates. + normalize_input : bool + If set, the input data is normalized along the channel dimension. + + References + ---------- + .. footbibliography:: + """ + + _n_clusters: Incomplete + _cluster_names: Incomplete + _ignore_polarity: bool + _normalize_input: Incomplete + _GEV_: Incomplete + + def __init__(self, n_clusters: int, normalize_input: bool = False) -> None: ... + def _repr_html_(self, caption: Incomplete | None = None): ... + def __eq__(self, other: Any) -> bool: + """Equality == method.""" + + def __ne__(self, other: Any) -> bool: + """Different != method.""" + + def _check_fit(self) -> None: + """Check if the cluster is fitted.""" + _cluster_centers_: Incomplete + _labels_: Incomplete + _fitted: bool + + def fit( + self, + inst: Union[BaseRaw, BaseEpochs], + picks: Picks = "eeg", + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + reject_by_annotation: bool = True, + *, + verbose: Optional[str] = None, + ) -> None: + """Compute cluster centers. + + Parameters + ---------- + inst : Raw | Epochs | ChData + MNE `~mne.io.Raw`, `~mne.Epochs` or `~pycrostates.io.ChData` object + from which to extract :term:`cluster centers`. + picks : str | list | slice | None + Channels to include. Note that all channels selected must have the same + type. Slices and lists of integers will be interpreted as channel indices. + In lists, channel name strings (e.g. ``['Fp1', 'Fp2']``) will pick the given + channels. Can also be the string values ``“all”`` to pick all channels, or + ``“data”`` to pick data channels. ``"eeg"`` (default) will pick all eeg + channels. Note that channels in ``info['bads']`` will be included if their + names or indices are explicitly provided. + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. + + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + """ + + def save(self, fname: Union[str, Path]): + """Save clustering solution to disk. + + Parameters + ---------- + fname : path-like + Path to the ``.fif`` file where the clustering solution is saved. + """ + + @staticmethod + def _aahc( + data: NDArray[float], + n_clusters: int, + ignore_polarity: bool, + normalize_input: bool, + ) -> tuple[float, NDArray[float], NDArray[int]]: + """Run the AAHC algorithm.""" + + @staticmethod + def _compute_maps( + data: NDArray[float], + n_clusters: int, + ignore_polarity: bool, + normalize_input: bool, + ) -> tuple[NDArray[float], NDArray[int]]: + """Compute microstates maps.""" + + @property + def normalize_input(self) -> bool: + """If set, the input data is normalized along the channel dimension. + + :type: `bool` + """ + + @property + def GEV_(self) -> float: + """Global Explained Variance. + + :type: `float` + """ + + @_BaseCluster.fitted.setter + def fitted(self, fitted) -> None: + """Fitted state. + + :type: `bool` + """ + + @staticmethod + def _check_ignore_polarity(ignore_polarity: bool) -> bool: + """Check that ignore_polarity is a boolean.""" + + @staticmethod + def _check_normalize_input(normalize_input: bool) -> bool: + """Check that normalize_input is a boolean.""" diff --git a/pycrostates/cluster/kmeans.pyi b/pycrostates/cluster/kmeans.pyi new file mode 100644 index 00000000..8d173412 --- /dev/null +++ b/pycrostates/cluster/kmeans.pyi @@ -0,0 +1,218 @@ +from pathlib import Path as Path +from typing import Any, Optional, Union + +from _typeshed import Incomplete +from mne import BaseEpochs as BaseEpochs +from mne.io import BaseRaw as BaseRaw +from numpy.random import Generator as Generator +from numpy.random import RandomState as RandomState +from numpy.typing import NDArray + +from .._typing import CHData as CHData +from .._typing import Picks as Picks +from .._typing import RANDomState as RANDomState +from ..utils import _corr_vectors as _corr_vectors +from ..utils._checks import _check_n_jobs as _check_n_jobs +from ..utils._checks import _check_random_state as _check_random_state +from ..utils._checks import _check_type as _check_type +from ..utils._docs import copy_doc as copy_doc +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger +from ._base import _BaseCluster as _BaseCluster + +class ModKMeans(_BaseCluster): + """Modified K-Means clustering algorithm. + + See :footcite:t:`Marqui1995` for additional information. + + Parameters + ---------- + n_clusters : int + The number of clusters, i.e. the number of microstates. + n_init : int + Number of time the k-means algorithm is run with different centroid seeds. The + final result will be the run with the highest Global Explained Variance (GEV). + max_iter : int + Maximum number of iterations of the K-means algorithm for a single run. + tol : float + Relative tolerance with regards estimate residual noise in the cluster centers + of two consecutive iterations to declare convergence. + random_state : None | int | instance of ~numpy.random.RandomState + A seed for the NumPy random number generator (RNG). If ``None`` (default), + the seed will be obtained from the operating system + (see :class:`~numpy.random.RandomState` for details), meaning it will most + likely produce different output every time this function or method is run. + To achieve reproducible results, pass a value here to explicitly initialize + the RNG with a defined state. + + References + ---------- + .. footbibliography:: + """ + + _n_clusters: Incomplete + _cluster_names: Incomplete + _n_init: Incomplete + _max_iter: Incomplete + _tol: Incomplete + _random_state: Incomplete + _GEV_: Incomplete + + def __init__( + self, + n_clusters: int, + n_init: int = 100, + max_iter: int = 300, + tol: Union[int, float] = 1e-06, + random_state: RANDomState = None, + ) -> None: ... + def _repr_html_(self, caption: Incomplete | None = None): ... + def __eq__(self, other: Any) -> bool: + """Equality == method.""" + + def __ne__(self, other: Any) -> bool: + """Different != method.""" + + def _check_fit(self) -> None: + """Check if the cluster is fitted.""" + _cluster_centers_: Incomplete + _labels_: Incomplete + _fitted: bool + _ignore_polarity: bool + + def fit( + self, + inst: Union[BaseRaw, BaseEpochs, CHData], + picks: Picks = "eeg", + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + reject_by_annotation: bool = True, + n_jobs: int = 1, + *, + verbose: Optional[str] = None, + ) -> None: + """Compute cluster centers. + + Parameters + ---------- + inst : Raw | Epochs | ChData + MNE `~mne.io.Raw`, `~mne.Epochs` or `~pycrostates.io.ChData` object from + which to extract :term:`cluster centers`. + picks : str | list | slice | None + Channels to include. Note that all channels selected must have the same + type. Slices and lists of integers will be interpreted as channel indices. + In lists, channel name strings (e.g. ``['Fp1', 'Fp2']``) will pick the given + channels. Can also be the string values ``“all”`` to pick all channels, or + ``“data”`` to pick data channels. ``"eeg"`` (default) will pick all eeg + channels. Note that channels in ``info['bads']`` will be included if their + names or indices are explicitly provided. + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. + + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. + n_jobs : int | None + The number of jobs to run in parallel. If ``-1``, it is set + to the number of CPU cores. Requires the :mod:`joblib` package. + ``None`` (default) is a marker for 'unset' that will be interpreted + as ``n_jobs=1`` (sequential execution) unless the call is performed under + a :class:`joblib:joblib.parallel_config` context manager that sets another + value for ``n_jobs``. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + """ + + def save(self, fname: Union[str, Path]): + """Save clustering solution to disk. + + Parameters + ---------- + fname : path-like + Path to the ``.fif`` file where the clustering solution is saved. + """ + + @staticmethod + def _kmeans( + data: NDArray[float], + n_clusters: int, + max_iter: int, + random_state: Union[RandomState, Generator], + tol: Union[int, float], + ) -> tuple[float, NDArray[float], NDArray[int], bool]: + """Run the k-means algorithm.""" + + @staticmethod + def _compute_maps( + data: NDArray[float], + n_clusters: int, + max_iter: int, + random_state: Union[RandomState, Generator], + tol: Union[int, float], + ) -> tuple[NDArray[float], bool]: + """Compute microstates maps. + + Based on mne_microstates by Marijn van Vliet + https://github.com/wmvanvliet/mne_microstates/blob/master/microstates.py + """ + + @property + def n_init(self) -> int: + """Number of k-means algorithms run with different centroid seeds. + + :type: `int` + """ + + @property + def max_iter(self) -> int: + """Maximum number of iterations of the k-means algorithm for a run. + + :type: `int` + """ + + @property + def tol(self) -> Union[int, float]: + """Relative tolerance to reach convergence. + + :type: `float` + """ + + @property + def random_state(self) -> Union[RandomState, Generator]: + """Random state to fix seed generation. + + :type: `~numpy.random.RandomState` | `~numpy.random.Generator` + """ + + @property + def GEV_(self) -> float: + """Global Explained Variance. + + :type: `float` + """ + + @_BaseCluster.fitted.setter + def fitted(self, fitted) -> None: + """Fitted state. + + :type: `bool` + """ + + @staticmethod + def _check_n_init(n_init: int) -> int: + """Check that n_init is a positive integer.""" + + @staticmethod + def _check_max_iter(max_iter: int) -> int: + """Check that max_iter is a positive integer.""" + + @staticmethod + def _check_tol(tol: Union[int, float]) -> Union[int, float]: + """Check that tol is a positive number.""" diff --git a/pycrostates/cluster/utils/__init__.pyi b/pycrostates/cluster/utils/__init__.pyi new file mode 100644 index 00000000..57630b0c --- /dev/null +++ b/pycrostates/cluster/utils/__init__.pyi @@ -0,0 +1,3 @@ +from .utils import optimize_order as optimize_order + +__all__: tuple[str, ...] diff --git a/pycrostates/cluster/utils/utils.pyi b/pycrostates/cluster/utils/utils.pyi new file mode 100644 index 00000000..44a00fd8 --- /dev/null +++ b/pycrostates/cluster/utils/utils.pyi @@ -0,0 +1,31 @@ +from numpy.typing import NDArray + +from ..._typing import Cluster as Cluster +from ...utils._checks import _check_type as _check_type +from ...utils._docs import fill_doc as fill_doc + +def _optimize_order( + centers: NDArray[float], + template_centers: NDArray[float], + ignore_polarity: bool = True, +): ... +def optimize_order(inst: Cluster, template_inst: Cluster): + """Optimize the order of cluster centers between two cluster instances. + + Optimize the order of cluster centers in an instance of a clustering algorithm to + maximize auto-correlation, based on a template instance as determined by the + Hungarian algorithm. The two cluster instances must have the same number of cluster + centers and the same polarity setting. + + Parameters + ---------- + inst : :ref:`cluster` + Fitted clustering algorithm to reorder. + template_inst : :ref:`cluster` + Fitted clustering algorithm to use as template for reordering. + + Returns + ------- + order : list of int + The new order to apply to inst to maximize auto-correlation of cluster centers. + """ diff --git a/pycrostates/datasets/__init__.pyi b/pycrostates/datasets/__init__.pyi new file mode 100644 index 00000000..934fcf9a --- /dev/null +++ b/pycrostates/datasets/__init__.pyi @@ -0,0 +1,3 @@ +from . import lemon as lemon + +__all__: tuple[str, ...] diff --git a/pycrostates/datasets/lemon/__init__.pyi b/pycrostates/datasets/lemon/__init__.pyi new file mode 100644 index 00000000..f9efd995 --- /dev/null +++ b/pycrostates/datasets/lemon/__init__.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete + +from .lemon import data_path as data_path +from .lemon import standardize as standardize + +__all__: Incomplete diff --git a/pycrostates/datasets/lemon/lemon.pyi b/pycrostates/datasets/lemon/lemon.pyi new file mode 100644 index 00000000..c9be379d --- /dev/null +++ b/pycrostates/datasets/lemon/lemon.pyi @@ -0,0 +1,71 @@ +from pathlib import Path + +from mne.io import BaseRaw + +from ...utils._checks import _check_type as _check_type +from ...utils._checks import _check_value as _check_value +from ...utils._config import get_config as get_config + +def data_path(subject_id: str, condition: str) -> Path: + """Get path to a local copy of preprocessed EEG recording from the LEMON dataset. + + Get path to a local copy of preprocessed EEG recording from the mind-brain-body + dataset of MRI, EEG, cognition, emotion, and peripheral physiology in young and old + adults\\ :footcite:p:`babayan_mind-brain-body_2019`. If there is no local copy of the + recording, this function will fetch it from the online repository and store it. The + default location is ``~/pycrostates_data``. + + Parameters + ---------- + subject_id : str + The subject id to use. For example ``'010276'``. + The list of available subjects can be found on this + `FTP server `_. + condition : str + Can be ``'EO'`` for eyes open condition or ``'EC'`` for eyes closed condition. + + Returns + ------- + path : Path + Path to a local copy of the requested recording. + + Notes + ----- + The lemon datasets is composed of EEGLAB files. To use the MNE reader + :func:`mne.io.read_raw_eeglab`, the ``pymatreader`` optional dependency + is required. Use the following installation method appropriate for your + environment: + + - ``pip install pymatreader`` + - ``conda install -c conda-forge pymatreader`` + + Note that an environment created via the MNE installers includes ``pymatreader`` by + default. + + References + ---------- + .. footbibliography:: + """ + +def standardize(raw: BaseRaw): + """Standardize :class:`~mne.io.Raw` from the lemon dataset. + + This function will interpolate missing channels from the standard setup, then + reorder channels and finally reference to a common average. + + Parameters + ---------- + raw : Raw + Raw data from the lemon dataset. + + Returns + ------- + raw : Raw + Standardize raw. + + Notes + ----- + If you don't want to interpolate missing channels, you can use + :func:`mne.channels.equalize_channels` instead to have the same electrodes across + different recordings. + """ diff --git a/pycrostates/io/__init__.pyi b/pycrostates/io/__init__.pyi new file mode 100644 index 00000000..f6aaf151 --- /dev/null +++ b/pycrostates/io/__init__.pyi @@ -0,0 +1,5 @@ +from .ch_data import ChData as ChData +from .meas_info import ChInfo as ChInfo +from .reader import read_cluster as read_cluster + +__all__: tuple[str, ...] diff --git a/pycrostates/io/ch_data.pyi b/pycrostates/io/ch_data.pyi new file mode 100644 index 00000000..d1dd8332 --- /dev/null +++ b/pycrostates/io/ch_data.pyi @@ -0,0 +1,128 @@ +from typing import Any, Union + +from _typeshed import Incomplete +from mne import Info +from numpy.typing import NDArray + +from .._typing import CHData as CHData +from .._typing import CHInfo as CHInfo +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils.mixin import ChannelsMixin as ChannelsMixin +from ..utils.mixin import ContainsMixin as ContainsMixin +from ..utils.mixin import MontageMixin as MontageMixin + +class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): + """ChData stores atemporal data with its spatial information. + + `~pycrostates.io.ChData` is similar to a raw instance where temporality has been + removed. Only the spatial information, stored as a `~pycrostates.io.ChInfo` is + retained. + + Parameters + ---------- + data : array + Data array of shape ``(n_channels, n_samples)``. + info : mne.Info | ChInfo + Atemporal measurement information. If a `mne.Info` is provided, it is converted + to a `~pycrostates.io.ChInfo`. + """ + + _data: Incomplete + _info: Incomplete + + def __init__(self, data: NDArray[float], info: Union[Info, CHInfo]) -> None: ... + def __repr__(self) -> str: + """String representation.""" + + def _repr_html_(self, caption: Incomplete | None = None): + """HTML representation.""" + + def __eq__(self, other: Any) -> bool: + """Equality == method.""" + + def __ne__(self, other: Any) -> bool: + """Different != method.""" + + def copy(self, deep: bool = True): + """Return a copy of the instance. + + Parameters + ---------- + deep : bool + If True, `~copy.deepcopy` is used instead of `~copy.copy`. + """ + + def get_data(self, picks: Incomplete | None = None) -> NDArray[float]: + """Retrieve the data array. + + Parameters + ---------- + picks : str | array-like | slice | None + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are + explicitly provided. + + Returns + ------- + data : array + Data array of shape ``(n_channels, n_samples)``. + """ + + def pick(self, picks, exclude: str = "bads"): + """Pick a subset of channels. + + Parameters + ---------- + picks : str | array-like | slice | None + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are + explicitly provided. + exclude : list | str + Set of channels to exclude, only used when picking based on types (e.g., + ``exclude="bads"`` when ``picks="meg"``). + + Returns + ------- + inst : ChData + The instance modified in-place. + """ + + def _get_channel_positions(self, picks: Incomplete | None = None): + """Get channel locations from info. + + Parameters + ---------- + picks : str | list | slice | None + None selects the good data channels. + + Returns + ------- + pos : array of shape (n_channels, 3) + Channel X/Y/Z locations. + """ + + @property + def info(self) -> CHInfo: + """Atemporal measurement information. + + :type: ChInfo + """ + + @property + def ch_names(self): + """Channel names.""" + + @property + def preload(self): + """Preload required by some MNE functions.""" diff --git a/pycrostates/io/fiff.pyi b/pycrostates/io/fiff.pyi new file mode 100644 index 00000000..77dac0fe --- /dev/null +++ b/pycrostates/io/fiff.pyi @@ -0,0 +1,125 @@ +from pathlib import Path as Path +from typing import Union + +from mne import Info +from numpy.typing import NDArray + +from .. import __version__ as __version__ +from .._typing import CHInfo as CHInfo +from ..cluster import AAHCluster as AAHCluster +from ..cluster import ModKMeans as ModKMeans +from ..utils._checks import _check_type as _check_type +from ..utils._checks import _check_value as _check_value +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +def _write_cluster( + fname: Union[str, Path], + cluster_centers_: NDArray[float], + chinfo: Union[CHInfo, Info], + algorithm: str, + cluster_names: list[str], + fitted_data: NDArray[float], + labels_: NDArray[int], + **kwargs, +): + """Save clustering solution to disk. + + Parameters + ---------- + fname : str | Path + Path to the ``.fif`` file where the clustering solution is saved. + cluster_centers_ : array + Cluster centers as a numpy array of shape (n_clusters, n_channels). + chinfo : ChInfo + Channel information (name, type, montage, ..) + algorithm : str + Clustering algorithm used. Valids are: + 'ModKMeans' + cluster_names : list + List of names for each of the clusters. + fitted_data : array + Data array used for fitting of shape (n_channels, n_samples) + labels_ : array + Array of labels for each sample of shape (n_samples, ) + """ + +def _prepare_kwargs(algorithm: str, kwargs: dict): + """Prepare params to save from kwargs.""" + +def _read_cluster(fname: Union[str, Path]): + """Read clustering solution from disk. + + Parameters + ---------- + fname : str | Path + Path to the ``.fif`` file where the clustering solution is saved. + + Returns + ------- + cluster : _BaseCluster + Loaded cluster solution. + version : str + pycrostates version used to save the cluster solution. + """ + +def _check_fit_parameters_and_variables(fit_parameters: dict, fit_variables: dict): + """Check that we have all the keys we are looking for and return algo.""" + +def _create_ModKMeans( + cluster_centers_: NDArray[float], + info: CHInfo, + cluster_names: list[str], + fitted_data: NDArray[float], + labels_: NDArray[int], + n_init: int, + max_iter: int, + tol: Union[int, float], + GEV_: float, +): + """Create a ModKMeans cluster.""" + +def _create_AAHCluster( + cluster_centers_: NDArray[float], + info: CHInfo, + cluster_names: list[str], + fitted_data: NDArray[float], + labels_: NDArray[int], + ignore_polarity: bool, + normalize_input: bool, + GEV_: float, +): + """Create a AAHCluster object.""" + +def _write_meas_info(fid, info: CHInfo): + """Write measurement info into a file id (from a fif file). + + Parameters + ---------- + fid : file + Open file descriptor. + info : ChInfo + Channel information. + """ + +def _read_meas_info(fid, tree): + """Read the measurement info. + + Parameters + ---------- + fid : file + Open file descriptor. + tree : tree + FIF tree structure. + + Returns + ------- + info : ChInfo + Channel information instance. + """ + +def _serialize(dict_: dict, outer_sep: str = ";", inner_sep: str = ":"): + """Aux function.""" + +def _deserialize(str_: str, outer_sep: str = ";", inner_sep: str = ":"): + """Aux Function.""" diff --git a/pycrostates/io/meas_info.pyi b/pycrostates/io/meas_info.pyi new file mode 100644 index 00000000..73b9834b --- /dev/null +++ b/pycrostates/io/meas_info.pyi @@ -0,0 +1,163 @@ +from typing import Optional, Union + +from mne import Info + +from .._typing import CHInfo as CHInfo +from ..utils._checks import _check_type as _check_type +from ..utils._checks import _IntLike as _IntLike +from ..utils._logs import logger as logger + +class ChInfo(CHInfo, Info): + """Atemporal measurement information. + + Similar to a :class:`mne.Info` class, but without any temporal information. + Only the channel-related information are present. A :class:`~pycrostates.io.ChInfo` + can be created either: + + - by providing a :class:`~mne.Info` class from which information are retrieved. + - by providing the ``ch_names`` and the ``ch_types`` to create a new instance. + + Only one of those 2 methods should be used at once. + + .. warning:: The only entry that should be manually changed by the user is + ``info['bads']``. All other entries should be considered read-only, + though they can be modified by various functions or methods (which have + safeguards to ensure all fields remain in sync). + + Parameters + ---------- + info : Info | None + MNE measurement information instance from which channel-related variables are + retrieved. + ch_names : list of str | int | None + Channel names. If an int, a list of channel names will be created from + ``range(ch_names)``. + ch_types : list of str | str | None + Channel types. If str, all channels are assumed to be of the same type. + + Attributes + ---------- + bads : list of str + List of bad (noisy/broken) channels, by name. These channels will by default be + ignored by many processing steps. + ch_names : tuple of str + The names of the channels. + chs : tuple of dict + A list of channel information dictionaries, one per channel. See Notes for more + information. + comps : list of dict + CTF software gradient compensation data. See Notes for more information. + ctf_head_t : dict | None + The transformation from 4D/CTF head coordinates to Neuromag head coordinates. + This is only present in 4D/CTF data. + custom_ref_applied : int + Whether a custom (=other than average) reference has been applied to the EEG + data. This flag is checked by some algorithms that require an average reference + to be set. + dev_ctf_t : dict | None + The transformation from device coordinates to 4D/CTF head coordinates. This is + only present in 4D/CTF data. + dev_head_t : dict | None + The device to head transformation. + dig : tuple of dict | None + The Polhemus digitization data in head coordinates. See Notes for more + information. + nchan : int + Number of channels. + projs : list of Projection + List of SSP operators that operate on the data. See :class:`mne.Projection` for + details. + + Notes + ----- + The following parameters have a nested structure. + + * ``chs`` list of dict: + + cal : float + The calibration factor to bring the channels to physical units. Used in + product with ``range`` to scale the data read from disk. + ch_name : str + The channel name. + coil_type : int + Coil type, e.g. ``FIFFV_COIL_MEG``. + coord_frame : int + The coordinate frame used, e.g. ``FIFFV_COORD_HEAD``. + kind : int + The kind of channel, e.g. ``FIFFV_EEG_CH``. + loc : array, shape (12,) + Channel location. For MEG this is the position plus the normal given by a + 3x3 rotation matrix. For EEG this is the position followed by reference + position (with 6 unused). The values are specified in device coordinates for + MEG and in head coordinates for EEG channels, respectively. + logno : int + Logical channel number, conventions in the usage of this number vary. + range : float + The hardware-oriented part of the calibration factor. This should be only + applied to the continuous raw data. Used in product with ``cal`` to scale + data read from disk. + scanno : int + Scanning order number, starting from 1. + unit : int + The unit to use, e.g. ``FIFF_UNIT_T_M``. + unit_mul : int + Unit multipliers, most commonly ``FIFF_UNITM_NONE``. + + * ``comps`` list of dict: + + ctfkind : int + CTF compensation grade. + colcals : ndarray + Column calibrations. + mat : dict + A named matrix dictionary (with entries "data", "col_names", etc.) + containing the compensation matrix. + rowcals : ndarray + Row calibrations. + save_calibrated : bool + Were the compensation data saved in calibrated form. + + * ``dig`` list of dict: + + kind : int + The kind of channel, e.g. ``FIFFV_POINT_EEG``, ``FIFFV_POINT_CARDINAL``. + r : array, shape (3,) + 3D position in m. and coord_frame. + ident : int + Number specifying the identity of the point. e.g. ``FIFFV_POINT_NASION`` if + kind is ``FIFFV_POINT_CARDINAL``, or 42 if kind is ``FIFFV_POINT_EEG``. + coord_frame : int + The coordinate frame used, e.g. ``FIFFV_COORD_HEAD``. + """ + + def __init__( + self, + info: Optional[Info] = None, + ch_names: Optional[Union[int, list[str], tuple[str, ...]]] = None, + ch_types: Optional[Union[str, list[str], tuple[str, ...]]] = None, + ) -> None: ... + def _init_from_info(self, info: Info): + """Init instance from mne Info.""" + _unlocked: bool + + def _init_from_channels( + self, + ch_names: Union[int, list[str], tuple[str, ...]], + ch_types: Union[str, list[str], tuple[str, ...]], + ): + """Init instance from channel names and types.""" + + def __getattribute__(self, name): + """Attribute getter.""" + + def __eq__(self, other): + """Equality == method.""" + + def __ne__(self, other): + """Different != method.""" + + def __deepcopy__(self, memodict): + """Make a deepcopy.""" + + def _check_consistency(self, prepend_error: str = ""): + """Do some self-consistency checks and datatype tweaks.""" diff --git a/pycrostates/io/reader.pyi b/pycrostates/io/reader.pyi new file mode 100644 index 00000000..b39bab4b --- /dev/null +++ b/pycrostates/io/reader.pyi @@ -0,0 +1,20 @@ +from pathlib import Path +from typing import Union + +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +def read_cluster(fname: Union[str, Path]): + """Read clustering solution from disk. + + Parameters + ---------- + fname : str | Path + Path to the ``.fif`` file where the clustering solution is saved. + + Returns + ------- + cluster : :ref:`Clustering` + Fitted clustering instance. + """ diff --git a/pycrostates/metrics/__init__.pyi b/pycrostates/metrics/__init__.pyi new file mode 100644 index 00000000..f2c61947 --- /dev/null +++ b/pycrostates/metrics/__init__.pyi @@ -0,0 +1,6 @@ +from .calinski_harabasz import calinski_harabasz_score as calinski_harabasz_score +from .davies_bouldin import davies_bouldin_score as davies_bouldin_score +from .dunn import dunn_score as dunn_score +from .silhouette import silhouette_score as silhouette_score + +__all__: tuple[str, ...] diff --git a/pycrostates/metrics/calinski_harabasz.pyi b/pycrostates/metrics/calinski_harabasz.pyi new file mode 100644 index 00000000..1e3c5cd4 --- /dev/null +++ b/pycrostates/metrics/calinski_harabasz.pyi @@ -0,0 +1,32 @@ +from ..cluster._base import _BaseCluster as _BaseCluster +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc + +def calinski_harabasz_score(cluster): + """Compute the Calinski-Harabasz score. + + This function computes the Calinski-Harabasz score\\ :footcite:p:`Calinski-Harabasz` + with :func:`sklearn.metrics.calinski_harabasz_score` from a fitted :ref:`Clustering` + instance. + + Parameters + ---------- + cluster : :ref:`cluster` + Fitted clustering algorithm from which to compute score. For more details about + current clustering implementations, check the :ref:`Clustering` section of the + documentation. + + Returns + ------- + score : float + The resulting Calinski-Harabasz score. + + Notes + ----- + For more details regarding the implementation, please refer to + :func:`sklearn.metrics.calinski_harabasz_score`. + + References + ---------- + .. footbibliography:: + """ diff --git a/pycrostates/metrics/davies_bouldin.pyi b/pycrostates/metrics/davies_bouldin.pyi new file mode 100644 index 00000000..ee1cb124 --- /dev/null +++ b/pycrostates/metrics/davies_bouldin.pyi @@ -0,0 +1,52 @@ +from ..cluster._base import _BaseCluster as _BaseCluster +from ..utils import _distance_matrix as _distance_matrix +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc + +def davies_bouldin_score(cluster): + """Compute the Davies-Bouldin score. + + This function computes the Davies-Bouldin score\\ :footcite:p:`Davies-Bouldin` with + :func:`sklearn.metrics.davies_bouldin_score` from a fitted :ref:`Clustering` + instance. + + Parameters + ---------- + cluster : :ref:`cluster` + Fitted clustering algorithm from which to compute score. For more details about + current clustering implementations, check the :ref:`Clustering` section of the + documentation. + + Returns + ------- + score : float + The resulting Davies-Bouldin score. + + Notes + ----- + For more details regarding the implementation, please refer to + :func:`sklearn.metrics.davies_bouldin_score`. This function was modified in order to + use the absolute spatial correlation for distance computations instead of the + euclidean distance. + + References + ---------- + .. footbibliography:: + """ + +def _davies_bouldin_score(X, labels): + """Compute the Davies-Bouldin score. + + Parameters + ---------- + X : array of shape (n_samples, n_features) + A list of ``n_features``-dimensional data points. Each row corresponds + to a single data point. + labels : array of shape (n_samples,) + Predicted labels for each sample. + + Returns + ------- + score: float + The resulting Davies-Bouldin score. + """ diff --git a/pycrostates/metrics/dunn.pyi b/pycrostates/metrics/dunn.pyi new file mode 100644 index 00000000..a55a740f --- /dev/null +++ b/pycrostates/metrics/dunn.pyi @@ -0,0 +1,49 @@ +from ..cluster._base import _BaseCluster as _BaseCluster +from ..utils import _distance_matrix as _distance_matrix +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc + +def dunn_score(cluster): + """Compute the Dunn index score. + + This function computes the Dunn index score\\ :footcite:p:`Dunn` from a + fitted :ref:`Clustering` instance. + + Parameters + ---------- + cluster : :ref:`cluster` + Fitted clustering algorithm from which to compute score. For more details about + current clustering implementations, check the :ref:`Clustering` section of the + documentation. + + Returns + ------- + score : float + The resulting Dunn score. + + Notes + ----- + This function uses the absolute spatial correlation for distance. + + References + ---------- + .. footbibliography:: + """ + +def _dunn_score(X, labels): + """Compute the Dunn index. + + Parameters + ---------- + X : np.array + np.array([N, p]) of all points + labels: np.array + np.array([N]) labels of all points + + Notes + ----- + Based on https://github.com/jqmviegas/jqm_cvi + """ + +def _delta_fast(ck, cl, distances): ... +def _big_delta_fast(ci, distances): ... diff --git a/pycrostates/metrics/silhouette.pyi b/pycrostates/metrics/silhouette.pyi new file mode 100644 index 00000000..092ab805 --- /dev/null +++ b/pycrostates/metrics/silhouette.pyi @@ -0,0 +1,34 @@ +from ..cluster._base import _BaseCluster as _BaseCluster +from ..utils import _distance_matrix as _distance_matrix +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc + +def silhouette_score(cluster): + """Compute the mean Silhouette Coefficient. + + This function computes the Silhouette Coefficient\\ :footcite:p:`Silhouettes` with + :func:`sklearn.metrics.silhouette_score` from a fitted :ref:`Clustering` instance. + + Parameters + ---------- + cluster : :ref:`cluster` + Fitted clustering algorithm from which to compute score. For more details about + current clustering implementations, check the :ref:`Clustering` section of the + documentation. + + Returns + ------- + silhouette : float + The mean Silhouette Coefficient. + + Notes + ----- + For more details regarding the implementation, please refer to + :func:`sklearn.metrics.silhouette_score`. + This proxy function uses ``metric="precomputed"`` with the absolute spatial + correlation for distance computations. + + References + ---------- + .. footbibliography:: + """ diff --git a/pycrostates/preprocessing/__init__.pyi b/pycrostates/preprocessing/__init__.pyi new file mode 100644 index 00000000..af15a317 --- /dev/null +++ b/pycrostates/preprocessing/__init__.pyi @@ -0,0 +1,5 @@ +from .extract_gfp_peaks import extract_gfp_peaks as extract_gfp_peaks +from .resample import resample as resample +from .spatial_filter import apply_spatial_filter as apply_spatial_filter + +__all__: tuple[str, ...] diff --git a/pycrostates/preprocessing/extract_gfp_peaks.pyi b/pycrostates/preprocessing/extract_gfp_peaks.pyi new file mode 100644 index 00000000..b7ac9075 --- /dev/null +++ b/pycrostates/preprocessing/extract_gfp_peaks.pyi @@ -0,0 +1,99 @@ +from typing import Optional, Union + +from _typeshed import Incomplete +from mne import BaseEpochs +from mne.io import BaseRaw +from numpy.typing import NDArray + +from .._typing import CHData as CHData +from .._typing import Picks as Picks +from ..utils._checks import _check_picks_uniqueness as _check_picks_uniqueness +from ..utils._checks import _check_reject_by_annotation as _check_reject_by_annotation +from ..utils._checks import _check_tmin_tmax as _check_tmin_tmax +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +def extract_gfp_peaks( + inst: Union[BaseRaw, BaseEpochs], + picks: Picks = "eeg", + return_all: bool = False, + min_peak_distance: int = 1, + tmin: Optional[float] = None, + tmax: Optional[float] = None, + reject_by_annotation: bool = True, + verbose: Incomplete | None = None, +) -> CHData: + """:term:`Global Field Power` (:term:`GFP`) peaks extraction. + + Extract :term:`Global Field Power` (:term:`GFP`) peaks from :class:`~mne.Epochs` or + :class:`~mne.io.Raw`. + + Parameters + ---------- + inst : Raw | Epochs + Instance from which to extract :term:`global field power` (GFP) peaks. + picks : str | list | slice | None + Channels to use for GFP computation. Note that all channels selected must have + the same type. Slices and lists of integers will be interpreted as channel + indices. In lists, channel name strings (e.g. ``['Fp1', 'Fp2']``) will pick the + given channels. Can also be the string values ``“all”`` to pick all channels, or + ``“data”`` to pick data channels. ``"eeg"`` (default) will pick all eeg + channels. Note that channels in ``info['bads']`` will be included if their + names or indices are explicitly provided. + return_all : bool + If True, the returned `~pycrostates.io.ChData` instance will include all + channels. If False (default), the returned `~pycrostates.io.ChData` instance + will only include channels used for GFP computation (i.e ``picks``). + min_peak_distance : int + Required minimal horizontal distance (``≥ 1`) in samples between neighboring + peaks. Smaller peaks are removed first until the condition is fulfilled for all + remaining peaks. Default to ``1``. + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. + + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + ch_data : ChData + Samples at global field power peaks. + + Notes + ----- + The :term:`Global Field Power` (:term:`GFP`) peaks are extracted with + :func:`scipy.signal.find_peaks`. Only the ``distance`` argument is filled with the + value provided in ``min_peak_distance``. The other arguments are set to their + default values. + """ + +def _extract_gfp_peaks( + data: NDArray[float], min_peak_distance: int = 2 +) -> NDArray[float]: + """Extract GFP peaks from input data. + + Parameters + ---------- + data : array of shape (n_channels, n_samples) + The data to extract GFP peaks from. + min_peak_distance : int + Required minimal horizontal distance (>= 1) in samples between neighboring + peaks. Smaller peaks are removed first until the condition is fulfilled for all + remaining peaks. Default to 2. + + Returns + ------- + peaks : array of shape (n_picks,) + The indices when peaks occur. + """ diff --git a/pycrostates/preprocessing/resample.pyi b/pycrostates/preprocessing/resample.pyi new file mode 100644 index 00000000..d68f3146 --- /dev/null +++ b/pycrostates/preprocessing/resample.pyi @@ -0,0 +1,91 @@ +from typing import Optional, Union + +from _typeshed import Incomplete +from mne import BaseEpochs +from mne.io import BaseRaw + +from .._typing import CHData as CHData +from .._typing import Picks as Picks +from .._typing import RANDomState as RANDomState +from ..utils._checks import _check_random_state as _check_random_state +from ..utils._checks import _check_reject_by_annotation as _check_reject_by_annotation +from ..utils._checks import _check_tmin_tmax as _check_tmin_tmax +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +def resample( + inst: Union[BaseRaw, BaseEpochs, CHData], + picks: Picks = None, + tmin: Optional[float] = None, + tmax: Optional[float] = None, + reject_by_annotation: bool = True, + n_resamples: int = None, + n_samples: int = None, + coverage: float = None, + replace: bool = True, + random_state: RANDomState = None, + verbose: Incomplete | None = None, +) -> list[CHData]: + """Resample a recording into epochs of random samples. + + Resample :class:`~mne.io.Raw`. :class:`~mne.Epochs` or + `~pycrostates.io.ChData` into ``n_resamples`` each containing ``n_samples`` + random samples of the original recording. + + Parameters + ---------- + inst : Raw | Epochs | ChData + Instance to resample. + picks : str | array-like | slice | None + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are + explicitly provided. + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + reject_by_annotation : bool + Whether to omit bad segments from the data before fitting. If ``True`` + (default), annotated segments whose description begins with ``'bad'`` are + omitted. If ``False``, no rejection based on annotations is performed. + + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. + n_resamples : int + Number of resamples to draw. Each epoch can be used to fit a separate clustering + solution. See notes for additional information. + n_samples : int + Length of each epoch (in samples). See notes for additional information. + coverage : float + Strictly positive ratio between resampling data size and size of the original + recording. See notes for additional information. + replace : bool + Whether or not to allow resampling with replacement. + random_state : None | int | instance of ~numpy.random.RandomState + A seed for the NumPy random number generator (RNG). If ``None`` (default), + the seed will be obtained from the operating system + (see :class:`~numpy.random.RandomState` for details), meaning it will most + likely produce different output every time this function or method is run. + To achieve reproducible results, pass a value here to explicitly initialize + the RNG with a defined state. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + resamples : list of :class:`~pycrostates.io.ChData` + List of resamples. + + Notes + ----- + Only two of ``n_resamples``, ``n_samples`` and ``coverage`` parameters must be + defined, the non-defined one will be determine at runtime by the 2 other parameters. + """ diff --git a/pycrostates/preprocessing/spatial_filter.pyi b/pycrostates/preprocessing/spatial_filter.pyi new file mode 100644 index 00000000..e49fe3a0 --- /dev/null +++ b/pycrostates/preprocessing/spatial_filter.pyi @@ -0,0 +1,93 @@ +from typing import Union + +from _typeshed import Incomplete +from mne import BaseEpochs +from mne.io import BaseRaw +from numpy.typing import NDArray as NDArray +from scipy.sparse import csr_matrix + +from .._typing import CHData as CHData +from ..utils._checks import _check_n_jobs as _check_n_jobs +from ..utils._checks import _check_type as _check_type +from ..utils._checks import _check_value as _check_value +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +def _check_adjacency(adjacency, info, ch_type): + """Check adjacency matrix.""" + +def apply_spatial_filter( + inst: Union[BaseRaw, BaseEpochs, CHData], + ch_type: str = "eeg", + exclude_bads: bool = True, + origin: Union[str, NDArray[float]] = "auto", + adjacency: Union[csr_matrix, str] = "auto", + n_jobs: int = 1, + verbose: Incomplete | None = None, +): + """Apply a spatial filter. + + Adapted from \\ :footcite:t:`michel2019eeg`. Apply an instantaneous filter which + interpolates channels with local neighbors while removing outliers. + The current implementation proceeds as follows: + + * An interpolation matrix is computed using + ``mne.channels.interpolation._make_interpolation_matrix``. + * An ajdacency matrix is computed using `mne.channels.find_ch_adjacency`. + * If ``exclude_bads`` is set to ``True``, bad channels are removed from the + ajdacency matrix. + * For each timepoint and each channel, a reduced adjacency matrix is built by + removing neighbors with lowest and highest value. + * For each timepoint and each channel, a reduced interpolation matrix is built by + extracting neighbor weights based on the reduced adjacency matrix. + * The reduced interpolation matrices are normalized. + * The channel's timepoints are interpolated using their reduced interpolation + matrix. + + Parameters + ---------- + inst : Raw | Epochs | ChData + Instance to filter spatially. + ch_type : str + The channel type on which to apply the spatial filter. Currently only supports + ``'eeg'``. + exclude_bads : bool + If set to ``True``, bad channels will be removed from the adjacency matrix and + therefore not used to interpolate neighbors. In addition, bad channels will not + be filtered. If set to ``False``, proceed as if all channels were good. + origin : array of shape (3,) | str + Origin of the sphere in the head coordinate frame and in meters. Can be + ``'auto'`` (default), which means a head-digitization-based origin fit. + adjacency : array or csr_matrix of shape (n_channels, n_channels) | str + An adjacency matrix. Can be created using `mne.channels.find_ch_adjacency` and + edited with `mne.viz.plot_ch_adjacency`. If ``'auto'`` (default), the matrix + will be automatically created using `mne.channels.find_ch_adjacency` and other + parameters. + n_jobs : int | None + The number of jobs to run in parallel. If ``-1``, it is set + to the number of CPU cores. Requires the :mod:`joblib` package. + ``None`` (default) is a marker for 'unset' that will be interpreted + as ``n_jobs=1`` (sequential execution) unless the call is performed under + a :class:`joblib:joblib.parallel_config` context manager that sets another + value for ``n_jobs``. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + inst : Raw | Epochs| ChData + The instance modified in place. + + Notes + ----- + This function requires a full copy of the data in memory. + + References + ---------- + .. footbibliography:: + """ + +def _channel_spatial_filter(index, data, adjacency_vector, interpolate_matrix): ... diff --git a/pycrostates/segmentation/__init__.pyi b/pycrostates/segmentation/__init__.pyi new file mode 100644 index 00000000..f126d94d --- /dev/null +++ b/pycrostates/segmentation/__init__.pyi @@ -0,0 +1,8 @@ +from .segmentation import EpochsSegmentation as EpochsSegmentation +from .segmentation import RawSegmentation as RawSegmentation +from .transitions import ( + compute_expected_transition_matrix as compute_expected_transition_matrix, +) +from .transitions import compute_transition_matrix as compute_transition_matrix + +__all__: tuple[str, ...] diff --git a/pycrostates/segmentation/_base.pyi b/pycrostates/segmentation/_base.pyi new file mode 100644 index 00000000..b4387401 --- /dev/null +++ b/pycrostates/segmentation/_base.pyi @@ -0,0 +1,217 @@ +from abc import abstractmethod +from typing import Optional, Union + +from _typeshed import Incomplete +from matplotlib.axes import Axes as Axes +from mne import BaseEpochs +from mne.io import BaseRaw +from numpy.typing import NDArray + +from .._typing import Segmentation as Segmentation +from ..utils import _corr_vectors as _corr_vectors +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger +from .transitions import ( + _compute_expected_transition_matrix as _compute_expected_transition_matrix, +) +from .transitions import _compute_transition_matrix as _compute_transition_matrix + +class _BaseSegmentation(Segmentation): + """Base class for a Microstates segmentation. + + Parameters + ---------- + labels : array of shape (n_samples, ) or (n_epochs, n_samples) + Microstates labels attributed to each sample, i.e. the segmentation. + inst : Raw | Epochs + MNE instance used to predict the segmentation. + cluster_centers : array (n_clusters, n_channels) + Clusters, i.e. the microstates maps used to compute the segmentation. + cluster_names : list | None + Name of the clusters. + predict_parameters : dict | None + The prediction parameters. + """ + + _labels: Incomplete + _inst: Incomplete + _cluster_centers_: Incomplete + _cluster_names: Incomplete + _predict_parameters: Incomplete + + @abstractmethod + def __init__( + self, + labels: NDArray[int], + inst: Union[BaseRaw, BaseEpochs], + cluster_centers_: NDArray[float], + cluster_names: Optional[list[str]] = None, + predict_parameters: Optional[dict] = None, + ): ... + def __repr__(self) -> str: ... + def _repr_html_(self, caption: Incomplete | None = None): ... + def compute_parameters(self, norm_gfp: bool = True, return_dist: bool = False): + """Compute microstate parameters. + + .. warning:: + + When working with `~mne.Epochs`, this method will put together segments of + all epochs. This could lead to wrong interpretation especially on state + durations. To avoid this behaviour, make sure to set the ``reject_edges`` + parameter to ``True`` when creating the segmentation. + + Parameters + ---------- + norm_gfp : bool + If True, the :term:`global field power` (GFP) is normalized. + return_dist : bool + If True, return the parameters distributions. + + Returns + ------- + dict : dict + Dictionaries containing microstate parameters as key/value pairs. + Keys are named as follow: ``'{microstate name}_{parameter name}'``. + + Available parameters are listed below: + + * ``mean_corr``: Mean correlation value for each time point assigned to a + given state. + * ``gev``: Global explained variance expressed by a given state. + It is the sum of global explained variance values of each time point + assigned to a given state. + * ``timecov``: Time coverage, the proportion of time during which + a given state is active. This metric is expressed as a ratio. + * ``meandurs``: Mean durations of segments assigned to a given + state. This metric is expressed in seconds (s). + * ``occurrences``: Occurrences per second, the mean number of + segment assigned to a given state per second. This metrics is expressed + in segment per second. + * ``dist_corr`` (req. ``return_dist=True``): Distribution of + correlations values of each time point assigned to a given state. + * ``dist_gev`` (req. ``return_dist=True``): Distribution of global + explained variances values of each time point assigned to a given state. + * ``dist_durs`` (req. ``return_dist=True``): Distribution of + durations of each segments assigned to a given state. Each value is + expressed in seconds (s). + """ + + def compute_transition_matrix( + self, stat: str = "probability", ignore_repetitions: bool = True + ): + """Compute the observed transition matrix. + + Count the number of transitions from one state to another and aggregate the + result as statistic. Transition "from" and "to" unlabeled segments ``-1`` are + ignored. + + Parameters + ---------- + %(stat_transition)s + %(ignore_repetitions)s + + Returns + ------- + %(transition_matrix)s + + Warnings + -------- + When working with `~mne.Epochs`, this method will take into account transitions + that occur between epochs. This could lead to wrong interpretation when working + with discontinuous data. To avoid this behaviour, make sure to set the + ``reject_edges`` parameter to ``True`` when predicting the segmentation. + """ + + def compute_expected_transition_matrix( + self, stat: str = "probability", ignore_repetitions: bool = True + ): + """Compute the expected transition matrix. + + Compute the theoretical transition matrix as if time course was ignored, but + microstate proportions kept (i.e. shuffled segmentation). This matrix can be + used to quantify/correct the effect of microstate time coverage on the observed + transition matrix obtained with the method + ``compute_expected_transition_matrix``. + Transition "from" and "to" unlabeled segments ``-1`` are ignored. + + Parameters + ---------- + stat : str + Aggregate statistic to compute transitions. Can be: + + * ``probability`` or ``proportion``: normalize count such as the probabilities along + the first axis is always equal to ``1``. + * ``percent``: normalize count such as the probabilities along the first axis is + always equal to ``100``. + ignore_repetitions : bool + If ``True``, ignores state repetitions. + For example, the input sequence ``AAABBCCD`` + will be transformed into ``ABCD`` before any calculation. + This is equivalent to setting the duration of all states to 1 sample. + + Returns + ------- + T : array of shape ``(n_cluster, n_cluster)`` + Array of transition probability values from one label to another. + First axis indicates state ``"from"``. Second axis indicates state ``"to"``. + """ + + def plot_cluster_centers( + self, axes: Optional[Union[Axes, NDArray[Axes]]] = None, block: bool = False + ): + """Plot cluster centers as topographic maps. + + Parameters + ---------- + axes : Axes | None + Either ``None`` to create a new figure or axes (or an array of axes) on which the + topographic map should be plotted. If the number of microstates maps to plot is + ``≥ 1``, an array of axes of size ``n_clusters`` should be provided. + block : bool + Whether to halt program execution until the figure is closed. + + Returns + ------- + fig : Figure + Matplotlib figure containing the topographic plots. + """ + + @staticmethod + def _check_cluster_names( + cluster_names: list[str], cluster_centers_: NDArray[float] + ): + """Check that the argument 'cluster_names' is valid.""" + + @staticmethod + def _check_predict_parameters(predict_parameters: dict): + """Check that the argument 'predict_parameters' is valid.""" + + @property + def predict_parameters(self) -> dict: + """Parameters used to predict the current segmentation. + + :type: `dict` + """ + + @property + def labels(self) -> NDArray[int]: + """Microstate label attributed to each sample (the segmentation). + + :type: `~numpy.array` + """ + + @property + def cluster_centers_(self) -> NDArray[float]: + """Cluster centers (i.e topographies) + used to compute the segmentation. + + :type: `~numpy.array` + """ + + @property + def cluster_names(self) -> list[str]: + """Name of the cluster centers. + + :type: `list` + """ diff --git a/pycrostates/segmentation/segmentation.pyi b/pycrostates/segmentation/segmentation.pyi new file mode 100644 index 00000000..d20f8147 --- /dev/null +++ b/pycrostates/segmentation/segmentation.pyi @@ -0,0 +1,133 @@ +from typing import Optional, Union + +from _typeshed import Incomplete +from matplotlib.axes import Axes as Axes +from mne import BaseEpochs +from mne.io import BaseRaw + +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..viz import plot_epoch_segmentation as plot_epoch_segmentation +from ..viz import plot_raw_segmentation as plot_raw_segmentation +from ._base import _BaseSegmentation as _BaseSegmentation + +class RawSegmentation(_BaseSegmentation): + """ + Contains the segmentation of a `~mne.io.Raw` instance. + + Parameters + ---------- + labels : array of shape ``(n_samples,)`` + Microstates labels attributed to each sample, i.e. the segmentation. + raw : Raw + `~mne.io.Raw` instance used for prediction. + cluster_centers : array (n_clusters, n_channels) + Clusters, i.e. the microstates maps used to compute the segmentation. + cluster_names : list | None + Name of the clusters. + predict_parameters : dict | None + The prediction parameters. + """ + + def __init__(self, *args, **kwargs) -> None: ... + def plot( + self, + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Optional[str] = None, + ): + """Plot the segmentation. + + Parameters + ---------- + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + cmap : str | colormap | None + The colormap to use. If None, ``viridis`` is used. + axes : Axes | None + Either ``None`` to create a new figure or axes on which the segmentation is + plotted. + cbar_axes : Axes | None + Axes on which to draw the colorbar, otherwise the colormap takes space from the main + axes. + block : bool + Whether to halt program execution until the figure is closed. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + fig : Figure + Matplotlib figure containing the segmentation. + """ + + @property + def raw(self) -> BaseRaw: + """`~mne.io.Raw` instance from which the segmentation was computed.""" + +class EpochsSegmentation(_BaseSegmentation): + """Contains the segmentation of an `~mne.Epochs` instance. + + Parameters + ---------- + labels : array of shape ``(n_epochs, n_samples)`` + Microstates labels attributed to each sample, i.e. the segmentation. + epochs : Epochs + `~mne.Epochs` instance used for prediction. + cluster_centers : array (n_clusters, n_channels) + Clusters, i.e. the microstates maps used to compute the segmentation. + cluster_names : list | None + Name of the clusters. + predict_parameters : dict | None + The prediction parameters. + """ + + def __init__(self, *args, **kwargs) -> None: ... + def plot( + self, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Incomplete | None = None, + ): + """Plot segmentation. + + Parameters + ---------- + cmap : str | colormap | None + The colormap to use. If None, ``viridis`` is used. + axes : Axes | None + Either ``None`` to create a new figure or axes on which the segmentation is + plotted. + cbar_axes : Axes | None + Axes on which to draw the colorbar, otherwise the colormap takes space from the main + axes. + block : bool + Whether to halt program execution until the figure is closed. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + fig : Figure + Matplotlib figure containing the segmentation. + """ + + @property + def epochs(self) -> BaseEpochs: + """`~mne.Epochs` instance from which the segmentation was computed.""" diff --git a/pycrostates/segmentation/transitions.pyi b/pycrostates/segmentation/transitions.pyi new file mode 100644 index 00000000..8f8ff1bd --- /dev/null +++ b/pycrostates/segmentation/transitions.pyi @@ -0,0 +1,106 @@ +from numpy.typing import NDArray + +from ..utils._checks import _check_type as _check_type +from ..utils._checks import _check_value as _check_value +from ..utils._docs import fill_doc as fill_doc + +def compute_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: + """Compute the observed transition matrix. + + Count the number of transitions from one state to another and aggregate the result + as statistic. Transitions "from" and "to" unlabeled segments ``-1`` are ignored. + + Parameters + ---------- + labels : array of shape ``(n_samples,)`` or ``(n_epochs, n_samples)`` + Microstates labels attributed to each sample, i.e. the segmentation. + n_clusters : int + The number of clusters, i.e. the number of microstates. + stat : str + Aggregate statistic to compute transitions. Can be: + + * ``count``: show the number of observations of each transition. + * ``probability`` or ``proportion``: normalize count such as the probabilities along + the first axis is always equal to ``1``. + * ``percent``: normalize count such as the probabilities along the first axis is + always equal to ``100``. + ignore_repetitions : bool + If ``True``, ignores state repetitions. + For example, the input sequence ``AAABBCCD`` + will be transformed into ``ABCD`` before any calculation. + This is equivalent to setting the duration of all states to 1 sample. + + Returns + ------- + T : array of shape ``(n_cluster, n_cluster)`` + Array of transition probability values from one label to another. + First axis indicates state ``"from"``. Second axis indicates state ``"to"``. + """ + +def _compute_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: + """Compute observed transition.""" + +def compute_expected_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: + """Compute the expected transition matrix. + + Compute the theoretical transition matrix as if time course was ignored, but + microstate proportions was kept (i.e. shuffled segmentation). This matrix can be + used to quantify/correct the effect of microstate time coverage on the observed + transition matrix obtained with the + :func:`pycrostates.segmentation.compute_transition_matrix`. + Transition "from" and "to" unlabeled segments ``-1`` are ignored. + + Parameters + ---------- + labels : array of shape ``(n_samples,)`` or ``(n_epochs, n_samples)`` + Microstates labels attributed to each sample, i.e. the segmentation. + n_clusters : int + The number of clusters, i.e. the number of microstates. + stat : str + Aggregate statistic to compute transitions. Can be: + + * ``probability`` or ``proportion``: normalize count such as the probabilities along + the first axis is always equal to ``1``. + * ``percent``: normalize count such as the probabilities along the first axis is + always equal to ``100``. + ignore_repetitions : bool + If ``True``, ignores state repetitions. + For example, the input sequence ``AAABBCCD`` + will be transformed into ``ABCD`` before any calculation. + This is equivalent to setting the duration of all states to 1 sample. + + Returns + ------- + T : array of shape ``(n_cluster, n_cluster)`` + Array of transition probability values from one label to another. + First axis indicates state ``"from"``. Second axis indicates state ``"to"``. + """ + +def _compute_expected_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: + """Compute theoretical transition matrix. + + The theoretical transition matrix takes into account the time coverage. + """ + +def _check_labels_n_clusters(labels: NDArray[int], n_clusters: int) -> None: + """Checker for labels and n_clusters.""" diff --git a/pycrostates/utils/__init__.pyi b/pycrostates/utils/__init__.pyi new file mode 100644 index 00000000..dfef719b --- /dev/null +++ b/pycrostates/utils/__init__.pyi @@ -0,0 +1,6 @@ +from ._config import get_config as get_config +from .utils import _compare_infos as _compare_infos +from .utils import _corr_vectors as _corr_vectors +from .utils import _distance_matrix as _distance_matrix + +__all__: tuple[str, ...] diff --git a/pycrostates/utils/_checks.pyi b/pycrostates/utils/_checks.pyi new file mode 100644 index 00000000..7afa9228 --- /dev/null +++ b/pycrostates/utils/_checks.pyi @@ -0,0 +1,118 @@ +from typing import Any + +from _typeshed import Incomplete + +from ._docs import fill_doc as fill_doc + +def _ensure_int(item, item_name: Incomplete | None = None): + """ + Ensure a variable is an integer. + + Parameters + ---------- + item : object + Item to check. + item_name : str | None + Name of the item to show inside the error message. + + Raises + ------ + TypeError + When the type of the item is not int. + """ + +class _IntLike: + @classmethod + def __instancecheck__(cls, other): ... + +class _Callable: + @classmethod + def __instancecheck__(cls, other): ... + +_types: Incomplete + +def _check_type(item, types, item_name: Incomplete | None = None): + """ + Check that item is an instance of types. + + Parameters + ---------- + item : object + Item to check. + types : tuple of types | tuple of str + Types to be checked against. + If str, must be one of: + ('int', 'str', 'numeric', 'path-like', 'callable') + item_name : str | None + Name of the item to show inside the error message. + + Raises + ------ + TypeError + When the type of the item is not one of the valid options. + """ + +def _check_value( + item, + allowed_values, + item_name: Incomplete | None = None, + extra: Incomplete | None = None, +): + """ + Check the value of a parameter against a list of valid options. + + Parameters + ---------- + item : object + Item to check. + allowed_values : tuple of objects + Allowed values to be checked against. + item_name : str | None + Name of the item to show inside the error message. + extra : str | None + Extra string to append to the invalid value sentence, e.g. "with ico mode". + + Raises + ------ + ValueError + When the value of the item is not one of the valid options. + """ + +def _check_n_jobs(n_jobs): + """Check n_jobs parameter. + + Check that n_jobs is a positive integer or a negative integer for all cores. CUDA is + not supported. + """ + +def _check_random_state(seed): + """Turn seed into a numpy.random.mtrand.RandomState instance.""" + +def _check_axes(axes): + """Check that ax is an Axes object or an array of Axes.""" + +def _check_reject_by_annotation(reject_by_annotation: bool) -> bool: + """Check the reject_by_annotation argument.""" + +def _check_tmin_tmax(inst, tmin, tmax): + """Check tmin/tmax compared to the provided instance.""" + +def _check_picks_uniqueness(info, picks) -> None: + """Check that the provided picks yield a single channel type.""" + +def _check_verbose(verbose: Any) -> int: + """Check that the value of verbose is valid. + + Parameters + ---------- + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + verbose : int + The verbosity level as an integer. + """ diff --git a/pycrostates/utils/_config.pyi b/pycrostates/utils/_config.pyi new file mode 100644 index 00000000..5e614283 --- /dev/null +++ b/pycrostates/utils/_config.pyi @@ -0,0 +1,38 @@ +from _typeshed import Incomplete + +def _get_user_dir(): + """Get user directory.""" + +def _get_home_dir(): + """Get pycrostates config directory.""" + +def _get_config_path(): + """Get config path.""" + +def _get_data_path(): + """Get pycrostates data directory.""" + +default_config: Incomplete + +def _save_config(config) -> None: + """Save pycrostates config.""" + +def get_config(): + """Read preferences from pycrostates' config file. + + Returns + ------- + config : dict + Dictionary containing all preferences as key/values pairs. + """ + +def set_config(key, value) -> None: + """Set preference key in the pycrostates' config file. + + Parameters + ---------- + key : str + The preference key to set. Must be a valid key. + value : str | None + The value to assign to the preference key. + """ diff --git a/pycrostates/utils/_docs.pyi b/pycrostates/utils/_docs.pyi new file mode 100644 index 00000000..e2bdd31b --- /dev/null +++ b/pycrostates/utils/_docs.pyi @@ -0,0 +1,70 @@ +from typing import Callable + +docdict: dict[str, str] +keys: tuple[str, ...] +entry: str +docdict_indented: dict[int, dict[str, str]] + +def fill_doc(f: Callable) -> Callable: + """Fill a docstring with docdict entries. + + Parameters + ---------- + f : callable + The function to fill the docstring of (modified in place). + + Returns + ------- + f : callable + The function, potentially with an updated __doc__. + """ + +def _indentcount_lines(lines: list[str]) -> int: + """Minimum indent for all lines in line list. + + >>> lines = [" one", " two", " three"] + >>> indentcount_lines(lines) + 1 + >>> lines = [] + >>> indentcount_lines(lines) + 0 + >>> lines = [" one"] + >>> indentcount_lines(lines) + 1 + >>> indentcount_lines([" "]) + 0 + """ + +def copy_doc(source: Callable) -> Callable: + """Copy the docstring from another function (decorator). + + The docstring of the source function is prepepended to the docstring of the + function wrapped by this decorator. + + This is useful when inheriting from a class and overloading a method. This + decorator can be used to copy the docstring of the original method. + + Parameters + ---------- + source : callable + The function to copy the docstring from. + + Returns + ------- + wrapper : callable + The decorated function. + + Examples + -------- + >>> class A: + ... def m1(): + ... '''Docstring for m1''' + ... pass + >>> class B(A): + ... @copy_doc(A.m1) + ... def m1(): + ... '''this gets appended''' + ... pass + >>> print(B.m1.__doc__) + Docstring for m1 this gets appended + """ diff --git a/pycrostates/utils/_fixes.pyi b/pycrostates/utils/_fixes.pyi new file mode 100644 index 00000000..08a8ce6f --- /dev/null +++ b/pycrostates/utils/_fixes.pyi @@ -0,0 +1,8 @@ +class _WrapStdOut: + """Dynamically wrap to sys.stdout. + + This makes packages that monkey-patch sys.stdout (e.g.doctest, sphinx-gallery) work + properly. + """ + + def __getattr__(self, name): ... diff --git a/pycrostates/utils/_imports.pyi b/pycrostates/utils/_imports.pyi new file mode 100644 index 00000000..96572acc --- /dev/null +++ b/pycrostates/utils/_imports.pyi @@ -0,0 +1,30 @@ +from ._logs import logger as logger + +_INSTALL_MAPPING: dict[str, str] + +def import_optional_dependency(name: str, extra: str = "", raise_error: bool = True): + """ + Import an optional dependency. + + By default, if a dependency is missing an ImportError with a nice message will be + raised. + + Parameters + ---------- + name : str + The module name. + extra : str + Additional text to include in the ImportError message. + raise_error : bool + What to do when a dependency is not found. + * True : If the module is not installed, raise an ImportError, otherwise, return + the module. + * False: If the module is not installed, issue a warning and return None, + otherwise, return the module. + + Returns + ------- + maybe_module : Optional[ModuleType] + The imported module when found. + None is returned when the package is not found and raise_error is False. + """ diff --git a/pycrostates/utils/_logs.pyi b/pycrostates/utils/_logs.pyi new file mode 100644 index 00000000..05bff187 --- /dev/null +++ b/pycrostates/utils/_logs.pyi @@ -0,0 +1,119 @@ +import logging +from pathlib import Path as Path +from typing import Callable, Optional, Union + +from _typeshed import Incomplete + +from ._checks import _check_type as _check_type +from ._checks import _check_verbose as _check_verbose +from ._docs import fill_doc as fill_doc +from ._fixes import _WrapStdOut as _WrapStdOut + +def _init_logger(*, verbose: Optional[Union[bool, str, int]] = None) -> logging.Logger: + """Initialize a logger. + + Assigns sys.stdout as the first handler of the logger. + + Parameters + ---------- + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + + Returns + ------- + logger : Logger + The initialized logger. + """ + +def add_file_handler( + fname: Union[str, Path], + mode: str = "a", + encoding: Optional[str] = None, + *, + verbose: Optional[Union[bool, str, int]] = None, +) -> None: + """Add a file handler to the logger. + + Parameters + ---------- + fname : str | Path + Path to the file where the logging output is saved. + mode : str + Mode in which the file is opened. + encoding : str | None + If not None, encoding used to open the file. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + """ + +def set_log_level( + verbose: Union[bool, str, int, None], apply_to_mne: bool = True +) -> None: + """Set the log level for the logger and the first handler ``sys.stdout``. + + Parameters + ---------- + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + apply_to_mne : bool + If True, also changes the log level of MNE. + """ + +class _LoggerFormatter(logging.Formatter): + """Format string Syntax for pycrostates.""" + + _formatters: Incomplete + + def __init__(self) -> None: ... + def format(self, record): + """ + Format the received log record. + + Parameters + ---------- + record : logging.LogRecord + """ + +def verbose(f: Callable) -> Callable: + """Set the verbose for the function call from the kwargs. + + Parameters + ---------- + f : callable + The function with a verbose argument. + + Returns + ------- + f : callable + The function. + """ + +class _use_log_level: + """Context manager to change the logging level temporary. + + Parameters + ---------- + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + """ + + _old_level: Incomplete + _level: Incomplete + + def __init__(self, verbose: Optional[Union[bool, str, int]] = None) -> None: ... + def __enter__(self) -> None: ... + def __exit__(self, *args) -> None: ... + +logger: Incomplete diff --git a/pycrostates/utils/mixin.pyi b/pycrostates/utils/mixin.pyi new file mode 100644 index 00000000..b0198197 --- /dev/null +++ b/pycrostates/utils/mixin.pyi @@ -0,0 +1,16 @@ +from mne.io.meas_info import ContainsMixin as MNEContainsMixin +from mne.io.meas_info import MontageMixin as MNEMontageMixin + +from ._docs import copy_doc as copy_doc + +class ChannelsMixin: + """Channels Mixin for futur implementation.""" + +class ContainsMixin(MNEContainsMixin): + def __contains__(self, ch_type) -> bool: ... + def __getattribute__(self, name): + """Attribute getter.""" + +class MontageMixin(MNEMontageMixin): + def __getattribute__(self, name): + """Attribute getter.""" diff --git a/pycrostates/utils/sys_info.pyi b/pycrostates/utils/sys_info.pyi new file mode 100644 index 00000000..7b9374a5 --- /dev/null +++ b/pycrostates/utils/sys_info.pyi @@ -0,0 +1,22 @@ +from typing import IO, Callable, Optional + +from packaging.requirements import Requirement + +from ._checks import _check_type as _check_type + +def sys_info(fid: Optional[IO] = None, developer: bool = False): + """Print the system information for debugging. + + Parameters + ---------- + fid : file-like | None + The file to write to, passed to :func:`print`. Can be None to use + :data:`sys.stdout`. + developer : bool + If True, display information about optional dependencies. + """ + +def _list_dependencies_info( + out: Callable, ljust: int, package: str, dependencies: list[Requirement] +): + """List dependencies names and versions.""" diff --git a/pycrostates/utils/utils.pyi b/pycrostates/utils/utils.pyi new file mode 100644 index 00000000..24ca6443 --- /dev/null +++ b/pycrostates/utils/utils.pyi @@ -0,0 +1,33 @@ +from _typeshed import Incomplete + +from ._logs import logger as logger + +def _corr_vectors(A, B, axis: int = 0): + """Compute pairwise correlation of multiple pairs of vectors. + + Fast way to compute correlation of multiple pairs of vectors without computing all + pairs as would with corr(A,B). Borrowed from Oli at StackOverflow. Note the + resulting coefficients vary slightly from the ones obtained from corr due to + differences in the order of the calculations. (Differences are of a magnitude of + 1e-9 to 1e-17 depending on the tested data). + + Parameters + ---------- + A : ndarray, shape (n, m) + The first collection of vectors + B : ndarray, shape (n, m) + The second collection of vectors + axis : int + The axis that contains the elements of each vector. Defaults to 0. + + Returns + ------- + corr : ndarray, shape (m, ) + For each pair of vectors, the correlation between them. + """ + +def _distance_matrix(X, Y: Incomplete | None = None): + """Distance matrix used in metrics.""" + +def _compare_infos(cluster_info, inst_info): + """Check that channels in cluster_info are all present in inst_info.""" diff --git a/pycrostates/viz/__init__.pyi b/pycrostates/viz/__init__.pyi new file mode 100644 index 00000000..2f038a90 --- /dev/null +++ b/pycrostates/viz/__init__.pyi @@ -0,0 +1,5 @@ +from .cluster_centers import plot_cluster_centers as plot_cluster_centers +from .segmentation import plot_epoch_segmentation as plot_epoch_segmentation +from .segmentation import plot_raw_segmentation as plot_raw_segmentation + +__all__: tuple[str, ...] diff --git a/pycrostates/viz/cluster_centers.pyi b/pycrostates/viz/cluster_centers.pyi new file mode 100644 index 00000000..40d64698 --- /dev/null +++ b/pycrostates/viz/cluster_centers.pyi @@ -0,0 +1,60 @@ +from typing import Any, Optional, Union + +from matplotlib.axes import Axes +from mne import Info +from numpy.typing import NDArray + +from .._typing import CHInfo as CHInfo +from ..utils._checks import _check_axes as _check_axes +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +_GRADIENT_KWARGS_DEFAULTS: dict[str, str] + +def plot_cluster_centers( + cluster_centers: NDArray[float], + info: Union[Info, CHInfo], + cluster_names: list[str] = None, + axes: Optional[Union[Axes, NDArray[Axes]]] = None, + show_gradient: Optional[bool] = False, + gradient_kwargs: dict[str, Any] = ..., + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, +): + """Create topographic maps for cluster centers. + + Parameters + ---------- + cluster_centers : array (n_clusters, n_channels) + Fitted clusters, i.e. the microstates maps. + info : Info | ChInfo + Info instance with a montage used to plot the topographic maps. + cluster_names : list | None + Name of the clusters. + axes : Axes | None + Either ``None`` to create a new figure or axes (or an array of axes) on which the + topographic map should be plotted. If the number of microstates maps to plot is + ``≥ 1``, an array of axes of size ``n_clusters`` should be provided. + show_gradient : bool + If True, plot a line between channel locations with highest and lowest values. + gradient_kwargs : dict + Additional keyword arguments passed to :meth:`matplotlib.axes.Axes.plot` to plot + gradient line. + block : bool + Whether to halt program execution until the figure is closed. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + **kwargs + Additional keyword arguments are passed to :func:`mne.viz.plot_topomap`. + + Returns + ------- + fig : Figure + Matplotlib figure(s) on which topographic maps are plotted. + """ diff --git a/pycrostates/viz/segmentation.pyi b/pycrostates/viz/segmentation.pyi new file mode 100644 index 00000000..7790751d --- /dev/null +++ b/pycrostates/viz/segmentation.pyi @@ -0,0 +1,138 @@ +from typing import Optional, Union + +from matplotlib import colors +from matplotlib.axes import Axes +from mne import BaseEpochs +from mne.io import BaseRaw +from numpy.typing import NDArray + +from ..utils._checks import _check_type as _check_type +from ..utils._docs import fill_doc as fill_doc +from ..utils._logs import logger as logger + +def plot_raw_segmentation( + labels: NDArray[int], + raw: BaseRaw, + n_clusters: int, + cluster_names: list[str] = None, + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, +): + """Plot raw segmentation. + + Parameters + ---------- + labels : array of shape ``(n_samples,)`` + Microstates labels attributed to each sample, i.e. the segmentation. + raw : Raw + MNE `~mne.io.Raw` instance. + n_clusters : int + The number of clusters, i.e. the number of microstates. + cluster_names : list | None + Name of the clusters. + tmin : float + Start time of the raw data to use in seconds (must be >= 0). + tmax : float + End time of the raw data to use in seconds (cannot exceed data duration). + cmap : str | colormap | None + The colormap to use. If None, ``viridis`` is used. + axes : Axes | None + Either ``None`` to create a new figure or axes on which the segmentation is + plotted. + cbar_axes : Axes | None + Axes on which to draw the colorbar, otherwise the colormap takes space from the main + axes. + block : bool + Whether to halt program execution until the figure is closed. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + **kwargs + Kwargs are passed to ``axes.plot``. + + Returns + ------- + fig : Figure + Matplotlib figure(s) on which topographic maps are plotted. + """ + +def plot_epoch_segmentation( + labels: NDArray[int], + epochs: BaseEpochs, + n_clusters: int, + cluster_names: list[str] = None, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, +): + """ + Plot epochs segmentation. + + Parameters + ---------- + labels : array of shape ``(n_epochs, n_samples)`` + Microstates labels attributed to each sample, i.e. the segmentation. + epochs : Epochs + MNE `~mne.Epochs` instance. + n_clusters : int + The number of clusters, i.e. the number of microstates. + cluster_names : list | None + Name of the clusters. + cmap : str | colormap | None + The colormap to use. If None, ``viridis`` is used. + axes : Axes | None + Either ``None`` to create a new figure or axes on which the segmentation is + plotted. + cbar_axes : Axes | None + Axes on which to draw the colorbar, otherwise the colormap takes space from the main + axes. + block : bool + Whether to halt program execution until the figure is closed. + verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True. + **kwargs + Kwargs are passed to ``axes.plot``. + + Returns + ------- + fig : Figure + Matplotlib figure on which topographic maps are plotted. + """ + +def _plot_segmentation( + labels: NDArray[int], + gfp: NDArray[float], + times: NDArray[float], + n_clusters: int, + cluster_names: list[str] = None, + cmap: Optional[Union[str, colors.Colormap]] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + verbose: Optional[str] = None, + **kwargs, +): + """Code snippet to plot segmentation for raw and epochs.""" + +def _compatibility_cmap(cmap: Optional[Union[str, colors.Colormap]], n_colors: int): + """Convert the 'cmap' argument to a colormap. + + Matplotlib 3.6 introduced a deprecation of plt.cm.get_cmap(). + When support for the 3.6 version is dropped, this checker can be removed. + """ From c6d58aaab7a5c125323652231856b58c665a4d3d Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Wed, 3 Jan 2024 14:11:32 +0100 Subject: [PATCH 11/26] order docdict alphabetically (#142) --- pycrostates/utils/_docs.py | 155 ++++++++++++++++----------- pycrostates/utils/tests/test_docs.py | 21 +++- 2 files changed, 115 insertions(+), 61 deletions(-) diff --git a/pycrostates/utils/_docs.py b/pycrostates/utils/_docs.py index 3aa9432a..5a5c138d 100644 --- a/pycrostates/utils/_docs.py +++ b/pycrostates/utils/_docs.py @@ -9,11 +9,11 @@ from mne.utils.docs import docdict as docdict_mne -# ------------------------- Documentation dictionary ------------------------- +# -- Documentation dictionary ---------------------------------------------------------- docdict: dict[str, str] = {} -# ---- Documentation to inc. from MNE ---- -keys: tuple[str, ...] = ( +# -- Documentation to inc. from MNE ---------------------------------------------------- +_KEYS_MNE: tuple[str, ...] = ( "n_jobs", "picks_all", "random_state", @@ -22,7 +22,7 @@ "reject_by_annotation_raw", ) -for key in keys: +for key in _KEYS_MNE: entry: str = docdict_mne[key] if ".. versionchanged::" in entry: entry = entry.replace(".. versionchanged::", ".. versionchanged:: MNE ") @@ -31,105 +31,140 @@ docdict[key] = entry del key -docdict["verbose"] = """ -verbose : int | str | bool | None - Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, - ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the - verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to - ``"WARNING"`` for False and to ``"INFO"`` for True.""" +# -- A --------------------------------------------------------------------------------- +docdict["axes_cbar"] = """ +cbar_axes : Axes | None + Axes on which to draw the colorbar, otherwise the colormap takes space from the main + axes.""" -# ---- Clusters ---- -docdict["n_clusters"] = """ -n_clusters : int - The number of clusters, i.e. the number of microstates. +docdict["axes_seg"] = """ +axes : Axes | None + Either ``None`` to create a new figure or axes on which the segmentation is + plotted.""" + +docdict["axes_topo"] = """ +axes : Axes | None + Either ``None`` to create a new figure or axes (or an array of axes) on which the + topographic map should be plotted. If the number of microstates maps to plot is + ``≥ 1``, an array of axes of size ``n_clusters`` should be provided.""" + +# -- B --------------------------------------------------------------------------------- +docdict["block"] = """ +block : bool + Whether to halt program execution until the figure is closed.""" + +# -- C --------------------------------------------------------------------------------- +docdict["cluster"] = """ +cluster : :ref:`cluster` + Fitted clustering algorithm from which to compute score. For more details about + current clustering implementations, check the :ref:`Clustering` section of the + documentation. """ + docdict["cluster_centers"] = """ cluster_centers : array (n_clusters, n_channels) Fitted clusters, i.e. the microstates maps.""" + +docdict["cluster_centers_seg"] = """ +cluster_centers : array (n_clusters, n_channels) + Clusters, i.e. the microstates maps used to compute the segmentation.""" + docdict["cluster_names"] = """ cluster_names : list | None Name of the clusters.""" -# ---- Metrics ----- -docdict["cluster"] = """ -cluster : :ref:`cluster` - Fitted clustering algorithm from which to compute score. For more details about - current clustering implementations, check the :ref:`Clustering` section of the - documentation. -""" +docdict["cmap"] = """ +cmap : str | colormap | None + The colormap to use. If None, ``viridis`` is used.""" -# ------ I/O ------- +# -- D --------------------------------------------------------------------------------- +# -- E --------------------------------------------------------------------------------- +# -- F --------------------------------------------------------------------------------- docdict["fname_fiff"] = """ fname : str | Path Path to the ``.fif`` file where the clustering solution is saved.""" -# -- Segmentation -- -docdict["cluster_centers_seg"] = """ -cluster_centers : array (n_clusters, n_channels) - Clusters, i.e. the microstates maps used to compute the segmentation.""" -docdict["labels_raw"] = """ -labels : array of shape ``(n_samples,)`` - Microstates labels attributed to each sample, i.e. the segmentation.""" +# -- G --------------------------------------------------------------------------------- +# -- H --------------------------------------------------------------------------------- +# -- I --------------------------------------------------------------------------------- +docdict["ignore_repetitions"] = """ +ignore_repetitions : bool + If ``True``, ignores state repetitions. + For example, the input sequence ``AAABBCCD`` + will be transformed into ``ABCD`` before any calculation. + This is equivalent to setting the duration of all states to 1 sample.""" + +# -- J --------------------------------------------------------------------------------- +# -- K --------------------------------------------------------------------------------- +# -- L --------------------------------------------------------------------------------- docdict["labels_epo"] = """ labels : array of shape ``(n_epochs, n_samples)`` Microstates labels attributed to each sample, i.e. the segmentation.""" + +docdict["labels_raw"] = """ +labels : array of shape ``(n_samples,)`` + Microstates labels attributed to each sample, i.e. the segmentation.""" + docdict["labels_transition"] = """ labels : array of shape ``(n_samples,)`` or ``(n_epochs, n_samples)`` Microstates labels attributed to each sample, i.e. the segmentation.""" -# TODO: predict_parameters docstring is missing. + +# -- M --------------------------------------------------------------------------------- +# -- N --------------------------------------------------------------------------------- +docdict["n_clusters"] = """ +n_clusters : int + The number of clusters, i.e. the number of microstates. +""" + +# -- O --------------------------------------------------------------------------------- +# -- P --------------------------------------------------------------------------------- docdict["predict_parameters"] = """ predict_parameters : dict | None The prediction parameters.""" -docdict["stat_transition"] = """ + +# -- Q --------------------------------------------------------------------------------- +# -- R --------------------------------------------------------------------------------- +# -- S --------------------------------------------------------------------------------- +docdict["stat_expected_transitions"] = """ stat : str Aggregate statistic to compute transitions. Can be: - * ``count``: show the number of observations of each transition. * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is always equal to ``100``.""" -docdict["stat_expected_transitions"] = """ + +docdict["stat_transition"] = """ stat : str Aggregate statistic to compute transitions. Can be: + * ``count``: show the number of observations of each transition. * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is always equal to ``100``.""" -docdict["ignore_repetitions"] = """ -ignore_repetitions : bool - If ``True``, ignores state repetitions. - For example, the input sequence ``AAABBCCD`` - will be transformed into ``ABCD`` before any calculation. - This is equivalent to setting the duration of all states to 1 sample.""" + +# -- T --------------------------------------------------------------------------------- docdict["transition_matrix"] = """ T : array of shape ``(n_cluster, n_cluster)`` Array of transition probability values from one label to another. First axis indicates state ``"from"``. Second axis indicates state ``"to"``.""" -# ------ Viz ------- -docdict["cmap"] = """ -cmap : str | colormap | None - The colormap to use. If None, ``viridis`` is used.""" -docdict["block"] = """ -block : bool - Whether to halt program execution until the figure is closed.""" -docdict["axes_topo"] = """ -axes : Axes | None - Either ``None`` to create a new figure or axes (or an array of axes) on which the - topographic map should be plotted. If the number of microstates maps to plot is - ``≥ 1``, an array of axes of size ``n_clusters`` should be provided.""" -docdict["axes_seg"] = """ -axes : Axes | None - Either ``None`` to create a new figure or axes on which the segmentation is - plotted.""" -docdict["axes_cbar"] = """ -cbar_axes : Axes | None - Axes on which to draw the colorbar, otherwise the colormap takes space from the main - axes.""" +# -- U --------------------------------------------------------------------------------- +# -- V --------------------------------------------------------------------------------- +docdict["verbose"] = """ +verbose : int | str | bool | None + Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, + ``"ERROR"``, ``"WARNING"``, ``"INFO"`` and ``"DEBUG"``. If None is provided, the + verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to + ``"WARNING"`` for False and to ``"INFO"`` for True.""" + +# -- W --------------------------------------------------------------------------------- +# -- X --------------------------------------------------------------------------------- +# -- Y --------------------------------------------------------------------------------- +# -- Z --------------------------------------------------------------------------------- -# ------------------------- Documentation functions -------------------------- +# -- Documentation functions ----------------------------------------------------------- docdict_indented: dict[int, dict[str, str]] = {} diff --git a/pycrostates/utils/tests/test_docs.py b/pycrostates/utils/tests/test_docs.py index cdcadaf4..a41d9ccb 100644 --- a/pycrostates/utils/tests/test_docs.py +++ b/pycrostates/utils/tests/test_docs.py @@ -1,8 +1,11 @@ """Test _docs.py""" +import re +from pathlib import Path + import pytest -from pycrostates.utils._docs import copy_doc, fill_doc +from pycrostates.utils._docs import _KEYS_MNE, copy_doc, docdict, fill_doc from pycrostates.utils._logs import verbose @@ -207,3 +210,19 @@ def method4(verbose=None): assert foo.method1.__doc__ == foo2.method2.__doc__ assert foo.method1.__doc__ == foo2.method3.__doc__ assert foo.method1.__doc__ == foo2.method4.__doc__ + + +def test_docdict_order(): + """Test that docdict is alphabetical.""" + # read the file as text, and get entries via regex + docdict_ = docdict.copy() + for key in _KEYS_MNE: + del docdict_[key] + docs_path = Path(__file__).parents[1] / "_docs.py" + assert docs_path.is_file() + with open(docs_path, "r", encoding="UTF-8") as fid: + docs = fid.read() + entries = re.findall(r'docdict\[(?:\n )?["\'](.+)["\']\n?\] = ', docs) + # test length, uniqueness and order + assert len(docdict_) == len(entries) + assert sorted(entries) == entries From 9fbebd7187125500c421e8977968a9cb463525ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Jan 2024 03:11:52 +0000 Subject: [PATCH 12/26] deploy stub files [ci skip] --- pycrostates/utils/_docs.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycrostates/utils/_docs.pyi b/pycrostates/utils/_docs.pyi index e2bdd31b..0cf2b0fa 100644 --- a/pycrostates/utils/_docs.pyi +++ b/pycrostates/utils/_docs.pyi @@ -1,7 +1,7 @@ from typing import Callable docdict: dict[str, str] -keys: tuple[str, ...] +_KEYS_MNE: tuple[str, ...] entry: str docdict_indented: dict[int, dict[str, str]] From 52224600fab28fe81e8ca50c8746494b7a900343 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Fri, 5 Jan 2024 14:54:32 +0100 Subject: [PATCH 13/26] Fix a couple of missing imports in __init__ files and improve caching in workflows (#143) * specify runner os in cache key * improve cache * fix a couple of imports * fix circular imports --- .github/workflows/pytest.yaml | 12 ++++++++++-- pycrostates/__init__.py | 2 +- pycrostates/io/fiff.py | 2 +- pycrostates/utils/__init__.py | 5 +++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 288b80a8..c4db7be5 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -43,12 +43,16 @@ jobs: run: | curl https://raw.githubusercontent.com/mne-tools/mne-testing-data/master/version.txt -o mne_testing_data_version.txt - name: Cache testing dataset + id: cache uses: actions/cache@v3 with: - key: mne-testing-${{ hashFiles('mne_testing_data_version.txt') }} + key: mne-testing-${{ runner.os }}-${{ hashFiles('mne_testing_data_version.txt') }} path: ~/mne_data - name: Download testing dataset + if: steps.cache.outputs.cache-hit != 'true' run: python -c "import mne; mne.datasets.testing.data_path(verbose=True)" + - name: Remove dataset version file + run: rm mne_testing_data_version.txt - name: Run pytest run: pytest pycrostates --cov=pycrostates --cov-report=xml --cov-config=pyproject.toml - name: Upload to codecov @@ -94,12 +98,16 @@ jobs: run: | curl https://raw.githubusercontent.com/mne-tools/mne-testing-data/master/version.txt -o mne_testing_data_version.txt - name: Cache testing dataset + id: cache uses: actions/cache@v3 with: - key: mne-testing-${{ hashFiles('mne_testing_data_version.txt') }} + key: mne-testing-${{ runner.os }}-${{ hashFiles('mne_testing_data_version.txt') }} path: ~/mne_data - name: Download testing dataset + if: steps.cache.outputs.cache-hit != 'true' run: python -c "import mne; mne.datasets.testing.data_path(verbose=True)" + - name: Remove dataset version file + run: rm mne_testing_data_version.txt - name: Run pytest run: pytest pycrostates --cov=pycrostates --cov-report=xml --cov-config=pyproject.toml - name: Upload to codecov diff --git a/pycrostates/__init__.py b/pycrostates/__init__.py index 950b6a86..194b7b08 100644 --- a/pycrostates/__init__.py +++ b/pycrostates/__init__.py @@ -1,6 +1,6 @@ """Pycrostates.""" -from . import cluster, datasets, metrics, preprocessing, utils, viz +from . import cluster, datasets, io, metrics, preprocessing, utils, viz from ._version import __version__ # noqa: F401 from .utils._logs import set_log_level from .utils.sys_info import sys_info # noqa: F401 diff --git a/pycrostates/io/fiff.py b/pycrostates/io/fiff.py index 3b56dbfa..5786c958 100644 --- a/pycrostates/io/fiff.py +++ b/pycrostates/io/fiff.py @@ -55,8 +55,8 @@ write_string, ) -from .. import __version__ from .._typing import CHInfo +from .._version import __version__ from ..cluster import AAHCluster, ModKMeans from ..utils._checks import _check_type, _check_value from ..utils._docs import fill_doc diff --git a/pycrostates/utils/__init__.py b/pycrostates/utils/__init__.py index 8877fae1..9939b16a 100644 --- a/pycrostates/utils/__init__.py +++ b/pycrostates/utils/__init__.py @@ -1,6 +1,7 @@ """Utils module for utilities.""" -from ._config import get_config +from . import sys_info # noqa: F401 +from ._config import get_config, set_config from .utils import _compare_infos, _corr_vectors, _distance_matrix # noqa: F401 -__all__: tuple[str, ...] = ("get_config",) +__all__: tuple[str, ...] = ("get_config", "set_config") From b1ca58f281a038024f0915017a13e6f2be65e057 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Jan 2024 03:10:59 +0000 Subject: [PATCH 14/26] deploy stub files [ci skip] --- pycrostates/__init__.pyi | 1 + pycrostates/io/fiff.pyi | 2 +- pycrostates/utils/__init__.pyi | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pycrostates/__init__.pyi b/pycrostates/__init__.pyi index 4f127561..2b005974 100644 --- a/pycrostates/__init__.pyi +++ b/pycrostates/__init__.pyi @@ -1,5 +1,6 @@ from . import cluster as cluster from . import datasets as datasets +from . import io as io from . import metrics as metrics from . import preprocessing as preprocessing from . import utils as utils diff --git a/pycrostates/io/fiff.pyi b/pycrostates/io/fiff.pyi index 77dac0fe..b3bacbaf 100644 --- a/pycrostates/io/fiff.pyi +++ b/pycrostates/io/fiff.pyi @@ -4,8 +4,8 @@ from typing import Union from mne import Info from numpy.typing import NDArray -from .. import __version__ as __version__ from .._typing import CHInfo as CHInfo +from .._version import __version__ as __version__ from ..cluster import AAHCluster as AAHCluster from ..cluster import ModKMeans as ModKMeans from ..utils._checks import _check_type as _check_type diff --git a/pycrostates/utils/__init__.pyi b/pycrostates/utils/__init__.pyi index dfef719b..3105d2aa 100644 --- a/pycrostates/utils/__init__.pyi +++ b/pycrostates/utils/__init__.pyi @@ -1,4 +1,6 @@ +from . import sys_info as sys_info from ._config import get_config as get_config +from ._config import set_config as set_config from .utils import _compare_infos as _compare_infos from .utils import _corr_vectors as _corr_vectors from .utils import _distance_matrix as _distance_matrix From f4cb1692ea0aeeaa0c6d71a60f343903d3d54d1b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:47:50 +0100 Subject: [PATCH 15/26] [pre-commit.ci] pre-commit autoupdate (#144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.1.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.1.11) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5bfc1b1b..91e0bbad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.1.11 hooks: - id: ruff name: ruff linter From d1bda8db6e9d4a0ed448bc67d60ce9063dfadfbb Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Mon, 15 Jan 2024 11:17:57 +0100 Subject: [PATCH 16/26] Enable ruff UP rules (#146) * enable ruff up rules * fix * exclude pyi globally --- pycrostates/cluster/_base.py | 2 +- pycrostates/conftest.py | 2 +- .../preprocessing/tests/test_spatial_filter.py | 2 +- pycrostates/segmentation/tests/test_segmentation.py | 2 +- pycrostates/utils/_checks.py | 11 ++++++----- pycrostates/utils/_config.py | 2 +- pycrostates/utils/_fixes.py | 2 +- pycrostates/utils/tests/test_docs.py | 2 +- pycrostates/utils/tests/test_logs.py | 2 +- pyproject.toml | 7 +++++-- 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pycrostates/cluster/_base.py b/pycrostates/cluster/_base.py index aaf3dfac..bbd84947 100644 --- a/pycrostates/cluster/_base.py +++ b/pycrostates/cluster/_base.py @@ -1086,7 +1086,7 @@ def _reject_edge_segments(segmentation: NDArray[int]) -> NDArray[int]: segmentation[:n] = -1 # set last segment to unlabeled - n = np.flip((segmentation != segmentation[-1])).argmax() + n = np.flip(segmentation != segmentation[-1]).argmax() segmentation[-n:] = -1 return segmentation diff --git a/pycrostates/conftest.py b/pycrostates/conftest.py index 953d89c1..e3e7cdc8 100644 --- a/pycrostates/conftest.py +++ b/pycrostates/conftest.py @@ -66,7 +66,7 @@ def matplotlib_config(): class CallbackRegistryReraise(orig): def __init__(self, exception_handler=None, signals=None): - super(CallbackRegistryReraise, self).__init__(exception_handler) + super().__init__(exception_handler) cbook.CallbackRegistry = CallbackRegistryReraise diff --git a/pycrostates/preprocessing/tests/test_spatial_filter.py b/pycrostates/preprocessing/tests/test_spatial_filter.py index 92119579..2dc7c2a2 100644 --- a/pycrostates/preprocessing/tests/test_spatial_filter.py +++ b/pycrostates/preprocessing/tests/test_spatial_filter.py @@ -129,7 +129,7 @@ def test_spatial_filter_custom_adjacency(): adjacency_matrix, ch_names = find_ch_adjacency(raw_all.info, "eeg") apply_spatial_filter(raw_all.copy(), "eeg", adjacency=adjacency_matrix) with pytest.raises(ValueError, match="Adjacency must have exactly 2 dimensions"): - apply_spatial_filter(raw_all.copy(), "eeg", adjacency=np.ones((len(ch_names)))) + apply_spatial_filter(raw_all.copy(), "eeg", adjacency=np.ones(len(ch_names))) with pytest.raises(ValueError, match="Adjacency must be of shape"): apply_spatial_filter( raw_all.copy(), "eeg", adjacency=adjacency_matrix[:-2, :-2] diff --git a/pycrostates/segmentation/tests/test_segmentation.py b/pycrostates/segmentation/tests/test_segmentation.py index f80d1385..fe195f28 100644 --- a/pycrostates/segmentation/tests/test_segmentation.py +++ b/pycrostates/segmentation/tests/test_segmentation.py @@ -205,7 +205,7 @@ def test_plot_segmentation(ModK, inst): ) def test_invalid_segmentation(Segmentation, inst, bad_inst, caplog): """Test that we can not create an invalid segmentation.""" - labels = np.zeros((inst.times.size)) + labels = np.zeros(inst.times.size) cluster_centers = np.zeros((4, len(inst.ch_names) - len(inst.info["bads"]))) cluster_names = ["a", "b", "c", "d"] diff --git a/pycrostates/utils/_checks.py b/pycrostates/utils/_checks.py index 5a6c8d2b..70850a40 100644 --- a/pycrostates/utils/_checks.py +++ b/pycrostates/utils/_checks.py @@ -42,7 +42,7 @@ def _ensure_int(item, item_name=None): item = int(operator.index(item)) except TypeError: item_name = "Item" if item_name is None else "'%s'" % item_name - raise TypeError("%s must be an int, got %s instead." % (item_name, type(item))) + raise TypeError(f"{item_name} must be an int, got {type(item)} instead.") return item @@ -161,9 +161,9 @@ def _check_value(item, allowed_values, item_name=None, extra=None): if len(allowed_values) == 1: options = "The only allowed value is %s" % repr(allowed_values[0]) elif len(allowed_values) == 2: - options = "Allowed values are %s and %s" % ( - repr(allowed_values[0]), - repr(allowed_values[1]), + options = ( + f"Allowed values are {repr(allowed_values[0])} " + f"and {repr(allowed_values[1])}" ) else: options = "Allowed values are " @@ -286,7 +286,8 @@ def _check_picks_uniqueness(info, picks): ch_types = info.get_channel_types(unique=False) ch_types, counts = np.unique(ch_types, return_counts=True) channels_msg = ", ".join( - "%s '%s' channel(s)" % t for t in zip(counts, ch_types) + "%s '%s' channel(s)" % t # noqa: UP031 + for t in zip(counts, ch_types) ) raise ValueError( f"Only one datatype can be selected, but 'picks' results in {channels_msg}." diff --git a/pycrostates/utils/_config.py b/pycrostates/utils/_config.py index 8e151413..e15d82ec 100644 --- a/pycrostates/utils/_config.py +++ b/pycrostates/utils/_config.py @@ -61,7 +61,7 @@ def get_config(): if not os.path.isfile(config_path): # create default config _save_config(default_config) - with open(config_path, "r", encoding="utf-8") as f: + with open(config_path, encoding="utf-8") as f: config = json.load(f) return config diff --git a/pycrostates/utils/_fixes.py b/pycrostates/utils/_fixes.py index 579aebce..27aa83ab 100644 --- a/pycrostates/utils/_fixes.py +++ b/pycrostates/utils/_fixes.py @@ -4,7 +4,7 @@ # https://github.com/sphinx-gallery/sphinx-gallery/issues/1112 -class _WrapStdOut(object): +class _WrapStdOut: """Dynamically wrap to sys.stdout. This makes packages that monkey-patch sys.stdout (e.g.doctest, sphinx-gallery) work diff --git a/pycrostates/utils/tests/test_docs.py b/pycrostates/utils/tests/test_docs.py index a41d9ccb..ebb9824c 100644 --- a/pycrostates/utils/tests/test_docs.py +++ b/pycrostates/utils/tests/test_docs.py @@ -220,7 +220,7 @@ def test_docdict_order(): del docdict_[key] docs_path = Path(__file__).parents[1] / "_docs.py" assert docs_path.is_file() - with open(docs_path, "r", encoding="UTF-8") as fid: + with open(docs_path, encoding="UTF-8") as fid: docs = fid.read() entries = re.findall(r'docdict\[(?:\n )?["\'](.+)["\']\n?\] = ', docs) # test length, uniqueness and order diff --git a/pycrostates/utils/tests/test_logs.py b/pycrostates/utils/tests/test_logs.py index 4a0d061f..4e1053e2 100644 --- a/pycrostates/utils/tests/test_logs.py +++ b/pycrostates/utils/tests/test_logs.py @@ -133,7 +133,7 @@ def test_file_handler(tmp_path): logger.handlers[-1].close() - with open(fname, mode="r") as file: + with open(fname) as file: lines = file.readlines() assert len(lines) == 2 diff --git a/pyproject.toml b/pyproject.toml index 55584f50..aa4a3f4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,18 +156,21 @@ minversion = '6.0' [tool.ruff] extend-exclude = [ + '*.pyi', 'doc', 'setup.py', ] +ignore = [ + "UP007", # 'Use `X | Y` for type annotations', requires python 3.10 +] line-length = 88 -select = ["E", "F", "W"] +select = ["E", "F", "UP", "W"] target-version = 'py39' [tool.ruff.format] docstring-code-format = true [tool.ruff.per-file-ignores] -'*.pyi' = ['E501'] '__init__.py' = ['F401'] [tool.setuptools] From a5da2b7165d778b91f2f811daa445e54280eb637 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:38:51 +0100 Subject: [PATCH 17/26] [pre-commit.ci] pre-commit autoupdate (#147) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.11 → v0.1.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.11...v0.1.13) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91e0bbad..95d3258f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.11 + rev: v0.1.13 hooks: - id: ruff name: ruff linter From 5e3b810801568e4d390f1c73997b5fc14703f5aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jan 2024 03:11:33 +0000 Subject: [PATCH 18/26] deploy stub files [ci skip] --- pycrostates/_typing.pyi | 3 +- pycrostates/cluster/_base.pyi | 116 +++--------------- pycrostates/cluster/aahc.pyi | 38 ++---- pycrostates/cluster/kmeans.pyi | 48 ++------ pycrostates/cluster/utils/utils.pyi | 10 +- pycrostates/datasets/lemon/lemon.pyi | 2 +- pycrostates/io/ch_data.pyi | 45 +++---- pycrostates/io/fiff.pyi | 40 +----- pycrostates/io/meas_info.pyi | 19 +-- pycrostates/io/reader.pyi | 2 +- pycrostates/metrics/calinski_harabasz.pyi | 2 +- pycrostates/metrics/davies_bouldin.pyi | 2 +- pycrostates/metrics/dunn.pyi | 7 +- pycrostates/metrics/silhouette.pyi | 2 +- .../preprocessing/extract_gfp_peaks.pyi | 19 +-- pycrostates/preprocessing/resample.pyi | 32 ++--- pycrostates/preprocessing/spatial_filter.pyi | 13 +- pycrostates/segmentation/_base.pyi | 42 +++---- pycrostates/segmentation/segmentation.pyi | 32 ++--- pycrostates/segmentation/transitions.pyi | 34 ++--- pycrostates/utils/_checks.pyi | 22 ++-- pycrostates/utils/_config.pyi | 3 +- pycrostates/utils/_docs.pyi | 2 +- pycrostates/utils/_fixes.pyi | 3 +- pycrostates/utils/_imports.pyi | 4 +- pycrostates/utils/_logs.pyi | 32 +++-- pycrostates/utils/mixin.pyi | 8 +- pycrostates/utils/sys_info.pyi | 8 +- pycrostates/utils/utils.pyi | 6 +- pycrostates/viz/cluster_centers.pyi | 15 +-- pycrostates/viz/segmentation.pyi | 46 +------ 31 files changed, 188 insertions(+), 469 deletions(-) diff --git a/pycrostates/_typing.pyi b/pycrostates/_typing.pyi index 7417a49c..52aeca59 100644 --- a/pycrostates/_typing.pyi +++ b/pycrostates/_typing.pyi @@ -15,6 +15,5 @@ class Cluster(ABC): class Segmentation(ABC): """Typing for a clustering class.""" - RANDomState = Optional[Union[int, RandomState, Generator]] -Picks = Optional[Union[str, NDArray[int]]] +Picks = Optional[Union[str, NDArray[int]]] \ No newline at end of file diff --git a/pycrostates/cluster/_base.pyi b/pycrostates/cluster/_base.pyi index ee4bf7f0..8ca0bbbc 100644 --- a/pycrostates/cluster/_base.pyi +++ b/pycrostates/cluster/_base.pyi @@ -28,7 +28,6 @@ from .utils import optimize_order as optimize_order class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): """Base Class for Microstates Clustering algorithms.""" - _n_clusters: Incomplete _cluster_names: Incomplete _cluster_centers_: Incomplete @@ -39,11 +38,13 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): _fitted: bool @abstractmethod - def __init__(self): ... + def __init__(self): + ... + def __repr__(self) -> str: """String representation.""" - def _repr_html_(self, caption: Incomplete | None = None): + def _repr_html_(self, caption: Incomplete | None=None): """HTML representation.""" def __eq__(self, other: Any) -> bool: @@ -52,7 +53,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): def __ne__(self, other: Any) -> bool: """Different != method.""" - def copy(self, deep: bool = True): + def copy(self, deep: bool=True): """Return a copy of the instance. Parameters @@ -68,16 +69,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): """Check if the cluster is unfitted.""" @abstractmethod - def fit( - self, - inst: Union[BaseRaw, BaseEpochs, CHData], - picks: Picks = "eeg", - tmin: Optional[Union[int, float]] = None, - tmax: Optional[Union[int, float]] = None, - reject_by_annotation: bool = True, - *, - verbose: Optional[str] = None, - ) -> NDArray[float]: + def fit(self, inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks='eeg', tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, reject_by_annotation: bool=True, *, verbose: Optional[str]=None) -> NDArray[float]: """Compute cluster centers. Parameters @@ -101,7 +93,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -110,11 +102,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): ``"WARNING"`` for False and to ``"INFO"`` for True. """ - def rename_clusters( - self, - mapping: Optional[dict[str, str]] = None, - new_names: Optional[Union[list[str], tuple[str, ...]]] = None, - ) -> None: + def rename_clusters(self, mapping: Optional[dict[str, str]]=None, new_names: Optional[Union[list[str], tuple[str, ...]]]=None) -> None: """Rename the clusters. Parameters @@ -131,12 +119,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Operates in-place. """ - def reorder_clusters( - self, - mapping: Optional[dict[int, int]] = None, - order: Optional[Union[list[int], tuple[int, ...], NDArray[int]]] = None, - template: Optional[Cluster] = None, - ) -> None: + def reorder_clusters(self, mapping: Optional[dict[int, int]]=None, order: Optional[Union[list[int], tuple[int, ...], NDArray[int]]]=None, template: Optional[Cluster]=None) -> None: """ Reorder the clusters of the fitted model. @@ -166,9 +149,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Operates in-place. """ - def invert_polarity( - self, invert: Union[bool, list[bool], tuple[bool, ...], NDArray[bool]] - ) -> None: + def invert_polarity(self, invert: Union[bool, list[bool], tuple[bool, ...], NDArray[bool]]) -> None: """Invert map polarities. Parameters @@ -188,20 +169,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): an article). """ - def plot( - self, - axes: Optional[Union[Axes, NDArray[Axes]]] = None, - show_gradient: Optional[bool] = False, - gradient_kwargs: dict[str, Any] = { - "color": "black", - "linestyle": "-", - "marker": "P", - }, - *, - block: bool = False, - verbose: Optional[str] = None, - **kwargs, - ): + def plot(self, axes: Optional[Union[Axes, NDArray[Axes]]]=None, show_gradient: Optional[bool]=False, gradient_kwargs: dict[str, Any]={'color': 'black', 'linestyle': '-', 'marker': 'P'}, *, block: bool=False, verbose: Optional[str]=None, **kwargs): """ Plot cluster centers as topographic maps. @@ -243,19 +211,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Path to the ``.fif`` file where the clustering solution is saved. """ - def predict( - self, - inst: Union[BaseRaw, BaseEpochs], - picks: Picks = None, - factor: int = 0, - half_window_size: int = 1, - tol: Union[int, float] = 1e-05, - min_segment_length: int = 0, - reject_edges: bool = True, - reject_by_annotation: bool = True, - *, - verbose: Optional[str] = None, - ): + def predict(self, inst: Union[BaseRaw, BaseEpochs], picks: Picks=None, factor: int=0, half_window_size: int=1, tol: Union[int, float]=1e-05, min_segment_length: int=0, reject_edges: bool=True, reject_by_annotation: bool=True, *, verbose: Optional[str]=None): """Segment `~mne.io.Raw` or `~mne.Epochs` into microstate sequence. Segment instance into microstate sequence using the segmentation smoothing @@ -293,7 +249,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -313,50 +269,18 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): .. footbibliography:: """ - def _predict_raw( - self, - raw: BaseRaw, - picks_data: NDArray[int], - factor: int, - tol: Union[int, float], - half_window_size: int, - min_segment_length: int, - reject_edges: bool, - reject_by_annotation: bool, - ) -> RawSegmentation: + def _predict_raw(self, raw: BaseRaw, picks_data: NDArray[int], factor: int, tol: Union[int, float], half_window_size: int, min_segment_length: int, reject_edges: bool, reject_by_annotation: bool) -> RawSegmentation: """Create segmentation for raw.""" - def _predict_epochs( - self, - epochs: BaseEpochs, - picks_data: NDArray[int], - factor: int, - tol: Union[int, float], - half_window_size: int, - min_segment_length: int, - reject_edges: bool, - ) -> EpochsSegmentation: + def _predict_epochs(self, epochs: BaseEpochs, picks_data: NDArray[int], factor: int, tol: Union[int, float], half_window_size: int, min_segment_length: int, reject_edges: bool) -> EpochsSegmentation: """Create segmentation for epochs.""" @staticmethod - def _segment( - data: NDArray[float], - states: NDArray[float], - factor: int, - tol: Union[int, float], - half_window_size: int, - ) -> NDArray[int]: + def _segment(data: NDArray[float], states: NDArray[float], factor: int, tol: Union[int, float], half_window_size: int) -> NDArray[int]: """Create segmentation. Must operate on a copy of states.""" @staticmethod - def _smooth_segmentation( - data: NDArray[float], - states: NDArray[float], - labels: NDArray[int], - factor: int, - tol: Union[int, float], - half_window_size: int, - ) -> NDArray[int]: + def _smooth_segmentation(data: NDArray[float], states: NDArray[float], labels: NDArray[int], factor: int, tol: Union[int, float], half_window_size: int) -> NDArray[int]: """Apply smoothing. Adapted from [1]. @@ -372,9 +296,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): """ @staticmethod - def _reject_short_segments( - segmentation: NDArray[int], data: NDArray[float], min_segment_length: int - ) -> NDArray[int]: + def _reject_short_segments(segmentation: NDArray[int], data: NDArray[float], min_segment_length: int) -> NDArray[int]: """Reject segments that are too short. Reject segments that are too short by replacing the labels with the adjacent @@ -452,4 +374,4 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): @staticmethod def _check_n_clusters(n_clusters: int) -> int: - """Check that the number of clusters is a positive integer.""" + """Check that the number of clusters is a positive integer.""" \ No newline at end of file diff --git a/pycrostates/cluster/aahc.pyi b/pycrostates/cluster/aahc.pyi index 72f416f0..79d06077 100644 --- a/pycrostates/cluster/aahc.pyi +++ b/pycrostates/cluster/aahc.pyi @@ -30,15 +30,18 @@ class AAHCluster(_BaseCluster): ---------- .. footbibliography:: """ - _n_clusters: Incomplete _cluster_names: Incomplete _ignore_polarity: bool _normalize_input: Incomplete _GEV_: Incomplete - def __init__(self, n_clusters: int, normalize_input: bool = False) -> None: ... - def _repr_html_(self, caption: Incomplete | None = None): ... + def __init__(self, n_clusters: int, normalize_input: bool=False) -> None: + ... + + def _repr_html_(self, caption: Incomplete | None=None): + ... + def __eq__(self, other: Any) -> bool: """Equality == method.""" @@ -51,16 +54,7 @@ class AAHCluster(_BaseCluster): _labels_: Incomplete _fitted: bool - def fit( - self, - inst: Union[BaseRaw, BaseEpochs], - picks: Picks = "eeg", - tmin: Optional[Union[int, float]] = None, - tmax: Optional[Union[int, float]] = None, - reject_by_annotation: bool = True, - *, - verbose: Optional[str] = None, - ) -> None: + def fit(self, inst: Union[BaseRaw, BaseEpochs], picks: Picks='eeg', tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, reject_by_annotation: bool=True, *, verbose: Optional[str]=None) -> None: """Compute cluster centers. Parameters @@ -84,7 +78,7 @@ class AAHCluster(_BaseCluster): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -103,21 +97,11 @@ class AAHCluster(_BaseCluster): """ @staticmethod - def _aahc( - data: NDArray[float], - n_clusters: int, - ignore_polarity: bool, - normalize_input: bool, - ) -> tuple[float, NDArray[float], NDArray[int]]: + def _aahc(data: NDArray[float], n_clusters: int, ignore_polarity: bool, normalize_input: bool) -> tuple[float, NDArray[float], NDArray[int]]: """Run the AAHC algorithm.""" @staticmethod - def _compute_maps( - data: NDArray[float], - n_clusters: int, - ignore_polarity: bool, - normalize_input: bool, - ) -> tuple[NDArray[float], NDArray[int]]: + def _compute_maps(data: NDArray[float], n_clusters: int, ignore_polarity: bool, normalize_input: bool) -> tuple[NDArray[float], NDArray[int]]: """Compute microstates maps.""" @property @@ -147,4 +131,4 @@ class AAHCluster(_BaseCluster): @staticmethod def _check_normalize_input(normalize_input: bool) -> bool: - """Check that normalize_input is a boolean.""" + """Check that normalize_input is a boolean.""" \ No newline at end of file diff --git a/pycrostates/cluster/kmeans.pyi b/pycrostates/cluster/kmeans.pyi index 8d173412..6b65f95c 100644 --- a/pycrostates/cluster/kmeans.pyi +++ b/pycrostates/cluster/kmeans.pyi @@ -49,7 +49,6 @@ class ModKMeans(_BaseCluster): ---------- .. footbibliography:: """ - _n_clusters: Incomplete _cluster_names: Incomplete _n_init: Incomplete @@ -58,15 +57,12 @@ class ModKMeans(_BaseCluster): _random_state: Incomplete _GEV_: Incomplete - def __init__( - self, - n_clusters: int, - n_init: int = 100, - max_iter: int = 300, - tol: Union[int, float] = 1e-06, - random_state: RANDomState = None, - ) -> None: ... - def _repr_html_(self, caption: Incomplete | None = None): ... + def __init__(self, n_clusters: int, n_init: int=100, max_iter: int=300, tol: Union[int, float]=1e-06, random_state: RANDomState=None) -> None: + ... + + def _repr_html_(self, caption: Incomplete | None=None): + ... + def __eq__(self, other: Any) -> bool: """Equality == method.""" @@ -80,17 +76,7 @@ class ModKMeans(_BaseCluster): _fitted: bool _ignore_polarity: bool - def fit( - self, - inst: Union[BaseRaw, BaseEpochs, CHData], - picks: Picks = "eeg", - tmin: Optional[Union[int, float]] = None, - tmax: Optional[Union[int, float]] = None, - reject_by_annotation: bool = True, - n_jobs: int = 1, - *, - verbose: Optional[str] = None, - ) -> None: + def fit(self, inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks='eeg', tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, reject_by_annotation: bool=True, n_jobs: int=1, *, verbose: Optional[str]=None) -> None: """Compute cluster centers. Parameters @@ -114,7 +100,7 @@ class ModKMeans(_BaseCluster): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. n_jobs : int | None The number of jobs to run in parallel. If ``-1``, it is set @@ -140,23 +126,11 @@ class ModKMeans(_BaseCluster): """ @staticmethod - def _kmeans( - data: NDArray[float], - n_clusters: int, - max_iter: int, - random_state: Union[RandomState, Generator], - tol: Union[int, float], - ) -> tuple[float, NDArray[float], NDArray[int], bool]: + def _kmeans(data: NDArray[float], n_clusters: int, max_iter: int, random_state: Union[RandomState, Generator], tol: Union[int, float]) -> tuple[float, NDArray[float], NDArray[int], bool]: """Run the k-means algorithm.""" @staticmethod - def _compute_maps( - data: NDArray[float], - n_clusters: int, - max_iter: int, - random_state: Union[RandomState, Generator], - tol: Union[int, float], - ) -> tuple[NDArray[float], bool]: + def _compute_maps(data: NDArray[float], n_clusters: int, max_iter: int, random_state: Union[RandomState, Generator], tol: Union[int, float]) -> tuple[NDArray[float], bool]: """Compute microstates maps. Based on mne_microstates by Marijn van Vliet @@ -215,4 +189,4 @@ class ModKMeans(_BaseCluster): @staticmethod def _check_tol(tol: Union[int, float]) -> Union[int, float]: - """Check that tol is a positive number.""" + """Check that tol is a positive number.""" \ No newline at end of file diff --git a/pycrostates/cluster/utils/utils.pyi b/pycrostates/cluster/utils/utils.pyi index 44a00fd8..c15ab1c7 100644 --- a/pycrostates/cluster/utils/utils.pyi +++ b/pycrostates/cluster/utils/utils.pyi @@ -4,11 +4,9 @@ from ..._typing import Cluster as Cluster from ...utils._checks import _check_type as _check_type from ...utils._docs import fill_doc as fill_doc -def _optimize_order( - centers: NDArray[float], - template_centers: NDArray[float], - ignore_polarity: bool = True, -): ... +def _optimize_order(centers: NDArray[float], template_centers: NDArray[float], ignore_polarity: bool=True): + ... + def optimize_order(inst: Cluster, template_inst: Cluster): """Optimize the order of cluster centers between two cluster instances. @@ -28,4 +26,4 @@ def optimize_order(inst: Cluster, template_inst: Cluster): ------- order : list of int The new order to apply to inst to maximize auto-correlation of cluster centers. - """ + """ \ No newline at end of file diff --git a/pycrostates/datasets/lemon/lemon.pyi b/pycrostates/datasets/lemon/lemon.pyi index c9be379d..61ba39ca 100644 --- a/pycrostates/datasets/lemon/lemon.pyi +++ b/pycrostates/datasets/lemon/lemon.pyi @@ -68,4 +68,4 @@ def standardize(raw: BaseRaw): If you don't want to interpolate missing channels, you can use :func:`mne.channels.equalize_channels` instead to have the same electrodes across different recordings. - """ + """ \ No newline at end of file diff --git a/pycrostates/io/ch_data.pyi b/pycrostates/io/ch_data.pyi index d1dd8332..d920412f 100644 --- a/pycrostates/io/ch_data.pyi +++ b/pycrostates/io/ch_data.pyi @@ -27,15 +27,16 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): Atemporal measurement information. If a `mne.Info` is provided, it is converted to a `~pycrostates.io.ChInfo`. """ - _data: Incomplete _info: Incomplete - def __init__(self, data: NDArray[float], info: Union[Info, CHInfo]) -> None: ... + def __init__(self, data: NDArray[float], info: Union[Info, CHInfo]) -> None: + ... + def __repr__(self) -> str: """String representation.""" - def _repr_html_(self, caption: Incomplete | None = None): + def _repr_html_(self, caption: Incomplete | None=None): """HTML representation.""" def __eq__(self, other: Any) -> bool: @@ -44,7 +45,7 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): def __ne__(self, other: Any) -> bool: """Different != method.""" - def copy(self, deep: bool = True): + def copy(self, deep: bool=True): """Return a copy of the instance. Parameters @@ -53,19 +54,19 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): If True, `~copy.deepcopy` is used instead of `~copy.copy`. """ - def get_data(self, picks: Incomplete | None = None) -> NDArray[float]: + def get_data(self, picks: Incomplete | None=None) -> NDArray[float]: """Retrieve the data array. Parameters ---------- picks : str | array-like | slice | None - Channels to include. Slices and lists of integers will be interpreted as - channel indices. In lists, channel *type* strings (e.g., ``['meg', - 'eeg']``) will pick channels of those types, channel *name* strings (e.g., - ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the - string values "all" to pick all channels, or "data" to pick :term:`data - channels`. None (default) will pick all channels. Note that channels in - ``info['bads']`` *will be included* if their names or indices are + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are explicitly provided. Returns @@ -74,19 +75,19 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): Data array of shape ``(n_channels, n_samples)``. """ - def pick(self, picks, exclude: str = "bads"): + def pick(self, picks, exclude: str='bads'): """Pick a subset of channels. Parameters ---------- picks : str | array-like | slice | None - Channels to include. Slices and lists of integers will be interpreted as - channel indices. In lists, channel *type* strings (e.g., ``['meg', - 'eeg']``) will pick channels of those types, channel *name* strings (e.g., - ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the - string values "all" to pick all channels, or "data" to pick :term:`data - channels`. None (default) will pick all channels. Note that channels in - ``info['bads']`` *will be included* if their names or indices are + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are explicitly provided. exclude : list | str Set of channels to exclude, only used when picking based on types (e.g., @@ -98,7 +99,7 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): The instance modified in-place. """ - def _get_channel_positions(self, picks: Incomplete | None = None): + def _get_channel_positions(self, picks: Incomplete | None=None): """Get channel locations from info. Parameters @@ -125,4 +126,4 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): @property def preload(self): - """Preload required by some MNE functions.""" + """Preload required by some MNE functions.""" \ No newline at end of file diff --git a/pycrostates/io/fiff.pyi b/pycrostates/io/fiff.pyi index b3bacbaf..bc7bee9e 100644 --- a/pycrostates/io/fiff.pyi +++ b/pycrostates/io/fiff.pyi @@ -13,16 +13,7 @@ from ..utils._checks import _check_value as _check_value from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def _write_cluster( - fname: Union[str, Path], - cluster_centers_: NDArray[float], - chinfo: Union[CHInfo, Info], - algorithm: str, - cluster_names: list[str], - fitted_data: NDArray[float], - labels_: NDArray[int], - **kwargs, -): +def _write_cluster(fname: Union[str, Path], cluster_centers_: NDArray[float], chinfo: Union[CHInfo, Info], algorithm: str, cluster_names: list[str], fitted_data: NDArray[float], labels_: NDArray[int], **kwargs): """Save clustering solution to disk. Parameters @@ -66,29 +57,10 @@ def _read_cluster(fname: Union[str, Path]): def _check_fit_parameters_and_variables(fit_parameters: dict, fit_variables: dict): """Check that we have all the keys we are looking for and return algo.""" -def _create_ModKMeans( - cluster_centers_: NDArray[float], - info: CHInfo, - cluster_names: list[str], - fitted_data: NDArray[float], - labels_: NDArray[int], - n_init: int, - max_iter: int, - tol: Union[int, float], - GEV_: float, -): +def _create_ModKMeans(cluster_centers_: NDArray[float], info: CHInfo, cluster_names: list[str], fitted_data: NDArray[float], labels_: NDArray[int], n_init: int, max_iter: int, tol: Union[int, float], GEV_: float): """Create a ModKMeans cluster.""" -def _create_AAHCluster( - cluster_centers_: NDArray[float], - info: CHInfo, - cluster_names: list[str], - fitted_data: NDArray[float], - labels_: NDArray[int], - ignore_polarity: bool, - normalize_input: bool, - GEV_: float, -): +def _create_AAHCluster(cluster_centers_: NDArray[float], info: CHInfo, cluster_names: list[str], fitted_data: NDArray[float], labels_: NDArray[int], ignore_polarity: bool, normalize_input: bool, GEV_: float): """Create a AAHCluster object.""" def _write_meas_info(fid, info: CHInfo): @@ -118,8 +90,8 @@ def _read_meas_info(fid, tree): Channel information instance. """ -def _serialize(dict_: dict, outer_sep: str = ";", inner_sep: str = ":"): +def _serialize(dict_: dict, outer_sep: str=';', inner_sep: str=':'): """Aux function.""" -def _deserialize(str_: str, outer_sep: str = ";", inner_sep: str = ":"): - """Aux Function.""" +def _deserialize(str_: str, outer_sep: str=';', inner_sep: str=':'): + """Aux Function.""" \ No newline at end of file diff --git a/pycrostates/io/meas_info.pyi b/pycrostates/io/meas_info.pyi index 73b9834b..2a9a9543 100644 --- a/pycrostates/io/meas_info.pyi +++ b/pycrostates/io/meas_info.pyi @@ -130,21 +130,14 @@ class ChInfo(CHInfo, Info): The coordinate frame used, e.g. ``FIFFV_COORD_HEAD``. """ - def __init__( - self, - info: Optional[Info] = None, - ch_names: Optional[Union[int, list[str], tuple[str, ...]]] = None, - ch_types: Optional[Union[str, list[str], tuple[str, ...]]] = None, - ) -> None: ... + def __init__(self, info: Optional[Info]=None, ch_names: Optional[Union[int, list[str], tuple[str, ...]]]=None, ch_types: Optional[Union[str, list[str], tuple[str, ...]]]=None) -> None: + ... + def _init_from_info(self, info: Info): """Init instance from mne Info.""" _unlocked: bool - def _init_from_channels( - self, - ch_names: Union[int, list[str], tuple[str, ...]], - ch_types: Union[str, list[str], tuple[str, ...]], - ): + def _init_from_channels(self, ch_names: Union[int, list[str], tuple[str, ...]], ch_types: Union[str, list[str], tuple[str, ...]]): """Init instance from channel names and types.""" def __getattribute__(self, name): @@ -159,5 +152,5 @@ class ChInfo(CHInfo, Info): def __deepcopy__(self, memodict): """Make a deepcopy.""" - def _check_consistency(self, prepend_error: str = ""): - """Do some self-consistency checks and datatype tweaks.""" + def _check_consistency(self, prepend_error: str=''): + """Do some self-consistency checks and datatype tweaks.""" \ No newline at end of file diff --git a/pycrostates/io/reader.pyi b/pycrostates/io/reader.pyi index b39bab4b..029be484 100644 --- a/pycrostates/io/reader.pyi +++ b/pycrostates/io/reader.pyi @@ -17,4 +17,4 @@ def read_cluster(fname: Union[str, Path]): ------- cluster : :ref:`Clustering` Fitted clustering instance. - """ + """ \ No newline at end of file diff --git a/pycrostates/metrics/calinski_harabasz.pyi b/pycrostates/metrics/calinski_harabasz.pyi index 1e3c5cd4..f7aa9ea6 100644 --- a/pycrostates/metrics/calinski_harabasz.pyi +++ b/pycrostates/metrics/calinski_harabasz.pyi @@ -29,4 +29,4 @@ def calinski_harabasz_score(cluster): References ---------- .. footbibliography:: - """ + """ \ No newline at end of file diff --git a/pycrostates/metrics/davies_bouldin.pyi b/pycrostates/metrics/davies_bouldin.pyi index ee1cb124..b229521f 100644 --- a/pycrostates/metrics/davies_bouldin.pyi +++ b/pycrostates/metrics/davies_bouldin.pyi @@ -49,4 +49,4 @@ def _davies_bouldin_score(X, labels): ------- score: float The resulting Davies-Bouldin score. - """ + """ \ No newline at end of file diff --git a/pycrostates/metrics/dunn.pyi b/pycrostates/metrics/dunn.pyi index a55a740f..20697f85 100644 --- a/pycrostates/metrics/dunn.pyi +++ b/pycrostates/metrics/dunn.pyi @@ -45,5 +45,8 @@ def _dunn_score(X, labels): Based on https://github.com/jqmviegas/jqm_cvi """ -def _delta_fast(ck, cl, distances): ... -def _big_delta_fast(ci, distances): ... +def _delta_fast(ck, cl, distances): + ... + +def _big_delta_fast(ci, distances): + ... \ No newline at end of file diff --git a/pycrostates/metrics/silhouette.pyi b/pycrostates/metrics/silhouette.pyi index 092ab805..0c5b6ee4 100644 --- a/pycrostates/metrics/silhouette.pyi +++ b/pycrostates/metrics/silhouette.pyi @@ -31,4 +31,4 @@ def silhouette_score(cluster): References ---------- .. footbibliography:: - """ + """ \ No newline at end of file diff --git a/pycrostates/preprocessing/extract_gfp_peaks.pyi b/pycrostates/preprocessing/extract_gfp_peaks.pyi index b7ac9075..29aa19b1 100644 --- a/pycrostates/preprocessing/extract_gfp_peaks.pyi +++ b/pycrostates/preprocessing/extract_gfp_peaks.pyi @@ -14,16 +14,7 @@ from ..utils._checks import _check_type as _check_type from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def extract_gfp_peaks( - inst: Union[BaseRaw, BaseEpochs], - picks: Picks = "eeg", - return_all: bool = False, - min_peak_distance: int = 1, - tmin: Optional[float] = None, - tmax: Optional[float] = None, - reject_by_annotation: bool = True, - verbose: Incomplete | None = None, -) -> CHData: +def extract_gfp_peaks(inst: Union[BaseRaw, BaseEpochs], picks: Picks='eeg', return_all: bool=False, min_peak_distance: int=1, tmin: Optional[float]=None, tmax: Optional[float]=None, reject_by_annotation: bool=True, verbose: Incomplete | None=None) -> CHData: """:term:`Global Field Power` (:term:`GFP`) peaks extraction. Extract :term:`Global Field Power` (:term:`GFP`) peaks from :class:`~mne.Epochs` or @@ -57,7 +48,7 @@ def extract_gfp_peaks( Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -78,9 +69,7 @@ def extract_gfp_peaks( default values. """ -def _extract_gfp_peaks( - data: NDArray[float], min_peak_distance: int = 2 -) -> NDArray[float]: +def _extract_gfp_peaks(data: NDArray[float], min_peak_distance: int=2) -> NDArray[float]: """Extract GFP peaks from input data. Parameters @@ -96,4 +85,4 @@ def _extract_gfp_peaks( ------- peaks : array of shape (n_picks,) The indices when peaks occur. - """ + """ \ No newline at end of file diff --git a/pycrostates/preprocessing/resample.pyi b/pycrostates/preprocessing/resample.pyi index d68f3146..11e3762c 100644 --- a/pycrostates/preprocessing/resample.pyi +++ b/pycrostates/preprocessing/resample.pyi @@ -14,19 +14,7 @@ from ..utils._checks import _check_type as _check_type from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def resample( - inst: Union[BaseRaw, BaseEpochs, CHData], - picks: Picks = None, - tmin: Optional[float] = None, - tmax: Optional[float] = None, - reject_by_annotation: bool = True, - n_resamples: int = None, - n_samples: int = None, - coverage: float = None, - replace: bool = True, - random_state: RANDomState = None, - verbose: Incomplete | None = None, -) -> list[CHData]: +def resample(inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks=None, tmin: Optional[float]=None, tmax: Optional[float]=None, reject_by_annotation: bool=True, n_resamples: int=None, n_samples: int=None, coverage: float=None, replace: bool=True, random_state: RANDomState=None, verbose: Incomplete | None=None) -> list[CHData]: """Resample a recording into epochs of random samples. Resample :class:`~mne.io.Raw`. :class:`~mne.Epochs` or @@ -38,13 +26,13 @@ def resample( inst : Raw | Epochs | ChData Instance to resample. picks : str | array-like | slice | None - Channels to include. Slices and lists of integers will be interpreted as - channel indices. In lists, channel *type* strings (e.g., ``['meg', - 'eeg']``) will pick channels of those types, channel *name* strings (e.g., - ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the - string values "all" to pick all channels, or "data" to pick :term:`data - channels`. None (default) will pick all channels. Note that channels in - ``info['bads']`` *will be included* if their names or indices are + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are explicitly provided. tmin : float Start time of the raw data to use in seconds (must be >= 0). @@ -54,7 +42,7 @@ def resample( Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. n_resamples : int Number of resamples to draw. Each epoch can be used to fit a separate clustering @@ -88,4 +76,4 @@ def resample( ----- Only two of ``n_resamples``, ``n_samples`` and ``coverage`` parameters must be defined, the non-defined one will be determine at runtime by the 2 other parameters. - """ + """ \ No newline at end of file diff --git a/pycrostates/preprocessing/spatial_filter.pyi b/pycrostates/preprocessing/spatial_filter.pyi index e49fe3a0..ee8f0ba5 100644 --- a/pycrostates/preprocessing/spatial_filter.pyi +++ b/pycrostates/preprocessing/spatial_filter.pyi @@ -16,15 +16,7 @@ from ..utils._logs import logger as logger def _check_adjacency(adjacency, info, ch_type): """Check adjacency matrix.""" -def apply_spatial_filter( - inst: Union[BaseRaw, BaseEpochs, CHData], - ch_type: str = "eeg", - exclude_bads: bool = True, - origin: Union[str, NDArray[float]] = "auto", - adjacency: Union[csr_matrix, str] = "auto", - n_jobs: int = 1, - verbose: Incomplete | None = None, -): +def apply_spatial_filter(inst: Union[BaseRaw, BaseEpochs, CHData], ch_type: str='eeg', exclude_bads: bool=True, origin: Union[str, NDArray[float]]='auto', adjacency: Union[csr_matrix, str]='auto', n_jobs: int=1, verbose: Incomplete | None=None): """Apply a spatial filter. Adapted from \\ :footcite:t:`michel2019eeg`. Apply an instantaneous filter which @@ -90,4 +82,5 @@ def apply_spatial_filter( .. footbibliography:: """ -def _channel_spatial_filter(index, data, adjacency_vector, interpolate_matrix): ... +def _channel_spatial_filter(index, data, adjacency_vector, interpolate_matrix): + ... \ No newline at end of file diff --git a/pycrostates/segmentation/_base.pyi b/pycrostates/segmentation/_base.pyi index b4387401..8342710c 100644 --- a/pycrostates/segmentation/_base.pyi +++ b/pycrostates/segmentation/_base.pyi @@ -33,7 +33,6 @@ class _BaseSegmentation(Segmentation): predict_parameters : dict | None The prediction parameters. """ - _labels: Incomplete _inst: Incomplete _cluster_centers_: Incomplete @@ -41,17 +40,16 @@ class _BaseSegmentation(Segmentation): _predict_parameters: Incomplete @abstractmethod - def __init__( - self, - labels: NDArray[int], - inst: Union[BaseRaw, BaseEpochs], - cluster_centers_: NDArray[float], - cluster_names: Optional[list[str]] = None, - predict_parameters: Optional[dict] = None, - ): ... - def __repr__(self) -> str: ... - def _repr_html_(self, caption: Incomplete | None = None): ... - def compute_parameters(self, norm_gfp: bool = True, return_dist: bool = False): + def __init__(self, labels: NDArray[int], inst: Union[BaseRaw, BaseEpochs], cluster_centers_: NDArray[float], cluster_names: Optional[list[str]]=None, predict_parameters: Optional[dict]=None): + ... + + def __repr__(self) -> str: + ... + + def _repr_html_(self, caption: Incomplete | None=None): + ... + + def compute_parameters(self, norm_gfp: bool=True, return_dist: bool=False): """Compute microstate parameters. .. warning:: @@ -97,9 +95,7 @@ class _BaseSegmentation(Segmentation): expressed in seconds (s). """ - def compute_transition_matrix( - self, stat: str = "probability", ignore_repetitions: bool = True - ): + def compute_transition_matrix(self, stat: str='probability', ignore_repetitions: bool=True): """Compute the observed transition matrix. Count the number of transitions from one state to another and aggregate the @@ -123,9 +119,7 @@ class _BaseSegmentation(Segmentation): ``reject_edges`` parameter to ``True`` when predicting the segmentation. """ - def compute_expected_transition_matrix( - self, stat: str = "probability", ignore_repetitions: bool = True - ): + def compute_expected_transition_matrix(self, stat: str='probability', ignore_repetitions: bool=True): """Compute the expected transition matrix. Compute the theoretical transition matrix as if time course was ignored, but @@ -139,7 +133,7 @@ class _BaseSegmentation(Segmentation): ---------- stat : str Aggregate statistic to compute transitions. Can be: - + * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is @@ -157,9 +151,7 @@ class _BaseSegmentation(Segmentation): First axis indicates state ``"from"``. Second axis indicates state ``"to"``. """ - def plot_cluster_centers( - self, axes: Optional[Union[Axes, NDArray[Axes]]] = None, block: bool = False - ): + def plot_cluster_centers(self, axes: Optional[Union[Axes, NDArray[Axes]]]=None, block: bool=False): """Plot cluster centers as topographic maps. Parameters @@ -178,9 +170,7 @@ class _BaseSegmentation(Segmentation): """ @staticmethod - def _check_cluster_names( - cluster_names: list[str], cluster_centers_: NDArray[float] - ): + def _check_cluster_names(cluster_names: list[str], cluster_centers_: NDArray[float]): """Check that the argument 'cluster_names' is valid.""" @staticmethod @@ -214,4 +204,4 @@ class _BaseSegmentation(Segmentation): """Name of the cluster centers. :type: `list` - """ + """ \ No newline at end of file diff --git a/pycrostates/segmentation/segmentation.pyi b/pycrostates/segmentation/segmentation.pyi index d20f8147..e243079f 100644 --- a/pycrostates/segmentation/segmentation.pyi +++ b/pycrostates/segmentation/segmentation.pyi @@ -29,18 +29,10 @@ class RawSegmentation(_BaseSegmentation): The prediction parameters. """ - def __init__(self, *args, **kwargs) -> None: ... - def plot( - self, - tmin: Optional[Union[int, float]] = None, - tmax: Optional[Union[int, float]] = None, - cmap: Optional[str] = None, - axes: Optional[Axes] = None, - cbar_axes: Optional[Axes] = None, - *, - block: bool = False, - verbose: Optional[str] = None, - ): + def __init__(self, *args, **kwargs) -> None: + ... + + def plot(self, tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Optional[str]=None): """Plot the segmentation. Parameters @@ -92,16 +84,10 @@ class EpochsSegmentation(_BaseSegmentation): The prediction parameters. """ - def __init__(self, *args, **kwargs) -> None: ... - def plot( - self, - cmap: Optional[str] = None, - axes: Optional[Axes] = None, - cbar_axes: Optional[Axes] = None, - *, - block: bool = False, - verbose: Incomplete | None = None, - ): + def __init__(self, *args, **kwargs) -> None: + ... + + def plot(self, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Incomplete | None=None): """Plot segmentation. Parameters @@ -130,4 +116,4 @@ class EpochsSegmentation(_BaseSegmentation): @property def epochs(self) -> BaseEpochs: - """`~mne.Epochs` instance from which the segmentation was computed.""" + """`~mne.Epochs` instance from which the segmentation was computed.""" \ No newline at end of file diff --git a/pycrostates/segmentation/transitions.pyi b/pycrostates/segmentation/transitions.pyi index 8f8ff1bd..a14c68f6 100644 --- a/pycrostates/segmentation/transitions.pyi +++ b/pycrostates/segmentation/transitions.pyi @@ -4,12 +4,7 @@ from ..utils._checks import _check_type as _check_type from ..utils._checks import _check_value as _check_value from ..utils._docs import fill_doc as fill_doc -def compute_transition_matrix( - labels: NDArray[int], - n_clusters: int, - stat: str = "probability", - ignore_repetitions: bool = True, -) -> NDArray[float]: +def compute_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: """Compute the observed transition matrix. Count the number of transitions from one state to another and aggregate the result @@ -23,7 +18,7 @@ def compute_transition_matrix( The number of clusters, i.e. the number of microstates. stat : str Aggregate statistic to compute transitions. Can be: - + * ``count``: show the number of observations of each transition. * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. @@ -42,20 +37,10 @@ def compute_transition_matrix( First axis indicates state ``"from"``. Second axis indicates state ``"to"``. """ -def _compute_transition_matrix( - labels: NDArray[int], - n_clusters: int, - stat: str = "probability", - ignore_repetitions: bool = True, -) -> NDArray[float]: +def _compute_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: """Compute observed transition.""" -def compute_expected_transition_matrix( - labels: NDArray[int], - n_clusters: int, - stat: str = "probability", - ignore_repetitions: bool = True, -) -> NDArray[float]: +def compute_expected_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: """Compute the expected transition matrix. Compute the theoretical transition matrix as if time course was ignored, but @@ -73,7 +58,7 @@ def compute_expected_transition_matrix( The number of clusters, i.e. the number of microstates. stat : str Aggregate statistic to compute transitions. Can be: - + * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is @@ -91,16 +76,11 @@ def compute_expected_transition_matrix( First axis indicates state ``"from"``. Second axis indicates state ``"to"``. """ -def _compute_expected_transition_matrix( - labels: NDArray[int], - n_clusters: int, - stat: str = "probability", - ignore_repetitions: bool = True, -) -> NDArray[float]: +def _compute_expected_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: """Compute theoretical transition matrix. The theoretical transition matrix takes into account the time coverage. """ def _check_labels_n_clusters(labels: NDArray[int], n_clusters: int) -> None: - """Checker for labels and n_clusters.""" + """Checker for labels and n_clusters.""" \ No newline at end of file diff --git a/pycrostates/utils/_checks.pyi b/pycrostates/utils/_checks.pyi index 7afa9228..d40c5740 100644 --- a/pycrostates/utils/_checks.pyi +++ b/pycrostates/utils/_checks.pyi @@ -4,7 +4,7 @@ from _typeshed import Incomplete from ._docs import fill_doc as fill_doc -def _ensure_int(item, item_name: Incomplete | None = None): +def _ensure_int(item, item_name: Incomplete | None=None): """ Ensure a variable is an integer. @@ -22,16 +22,19 @@ def _ensure_int(item, item_name: Incomplete | None = None): """ class _IntLike: + @classmethod - def __instancecheck__(cls, other): ... + def __instancecheck__(cls, other): + ... class _Callable: - @classmethod - def __instancecheck__(cls, other): ... + @classmethod + def __instancecheck__(cls, other): + ... _types: Incomplete -def _check_type(item, types, item_name: Incomplete | None = None): +def _check_type(item, types, item_name: Incomplete | None=None): """ Check that item is an instance of types. @@ -52,12 +55,7 @@ def _check_type(item, types, item_name: Incomplete | None = None): When the type of the item is not one of the valid options. """ -def _check_value( - item, - allowed_values, - item_name: Incomplete | None = None, - extra: Incomplete | None = None, -): +def _check_value(item, allowed_values, item_name: Incomplete | None=None, extra: Incomplete | None=None): """ Check the value of a parameter against a list of valid options. @@ -115,4 +113,4 @@ def _check_verbose(verbose: Any) -> int: ------- verbose : int The verbosity level as an integer. - """ + """ \ No newline at end of file diff --git a/pycrostates/utils/_config.pyi b/pycrostates/utils/_config.pyi index 5e614283..464c9f1d 100644 --- a/pycrostates/utils/_config.pyi +++ b/pycrostates/utils/_config.pyi @@ -11,7 +11,6 @@ def _get_config_path(): def _get_data_path(): """Get pycrostates data directory.""" - default_config: Incomplete def _save_config(config) -> None: @@ -35,4 +34,4 @@ def set_config(key, value) -> None: The preference key to set. Must be a valid key. value : str | None The value to assign to the preference key. - """ + """ \ No newline at end of file diff --git a/pycrostates/utils/_docs.pyi b/pycrostates/utils/_docs.pyi index 0cf2b0fa..7deb289f 100644 --- a/pycrostates/utils/_docs.pyi +++ b/pycrostates/utils/_docs.pyi @@ -67,4 +67,4 @@ def copy_doc(source: Callable) -> Callable: ... pass >>> print(B.m1.__doc__) Docstring for m1 this gets appended - """ + """ \ No newline at end of file diff --git a/pycrostates/utils/_fixes.pyi b/pycrostates/utils/_fixes.pyi index 08a8ce6f..7b1dd3e8 100644 --- a/pycrostates/utils/_fixes.pyi +++ b/pycrostates/utils/_fixes.pyi @@ -5,4 +5,5 @@ class _WrapStdOut: properly. """ - def __getattr__(self, name): ... + def __getattr__(self, name): + ... \ No newline at end of file diff --git a/pycrostates/utils/_imports.pyi b/pycrostates/utils/_imports.pyi index 96572acc..7de6b417 100644 --- a/pycrostates/utils/_imports.pyi +++ b/pycrostates/utils/_imports.pyi @@ -2,7 +2,7 @@ from ._logs import logger as logger _INSTALL_MAPPING: dict[str, str] -def import_optional_dependency(name: str, extra: str = "", raise_error: bool = True): +def import_optional_dependency(name: str, extra: str='', raise_error: bool=True): """ Import an optional dependency. @@ -27,4 +27,4 @@ def import_optional_dependency(name: str, extra: str = "", raise_error: bool = T maybe_module : Optional[ModuleType] The imported module when found. None is returned when the package is not found and raise_error is False. - """ + """ \ No newline at end of file diff --git a/pycrostates/utils/_logs.pyi b/pycrostates/utils/_logs.pyi index 05bff187..687db359 100644 --- a/pycrostates/utils/_logs.pyi +++ b/pycrostates/utils/_logs.pyi @@ -9,7 +9,7 @@ from ._checks import _check_verbose as _check_verbose from ._docs import fill_doc as fill_doc from ._fixes import _WrapStdOut as _WrapStdOut -def _init_logger(*, verbose: Optional[Union[bool, str, int]] = None) -> logging.Logger: +def _init_logger(*, verbose: Optional[Union[bool, str, int]]=None) -> logging.Logger: """Initialize a logger. Assigns sys.stdout as the first handler of the logger. @@ -28,13 +28,7 @@ def _init_logger(*, verbose: Optional[Union[bool, str, int]] = None) -> logging. The initialized logger. """ -def add_file_handler( - fname: Union[str, Path], - mode: str = "a", - encoding: Optional[str] = None, - *, - verbose: Optional[Union[bool, str, int]] = None, -) -> None: +def add_file_handler(fname: Union[str, Path], mode: str='a', encoding: Optional[str]=None, *, verbose: Optional[Union[bool, str, int]]=None) -> None: """Add a file handler to the logger. Parameters @@ -52,9 +46,7 @@ def add_file_handler( ``"WARNING"`` for False and to ``"INFO"`` for True. """ -def set_log_level( - verbose: Union[bool, str, int, None], apply_to_mne: bool = True -) -> None: +def set_log_level(verbose: Union[bool, str, int, None], apply_to_mne: bool=True) -> None: """Set the log level for the logger and the first handler ``sys.stdout``. Parameters @@ -70,10 +62,11 @@ def set_log_level( class _LoggerFormatter(logging.Formatter): """Format string Syntax for pycrostates.""" - _formatters: Incomplete - def __init__(self) -> None: ... + def __init__(self) -> None: + ... + def format(self, record): """ Format the received log record. @@ -108,12 +101,15 @@ class _use_log_level: verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to ``"WARNING"`` for False and to ``"INFO"`` for True. """ - _old_level: Incomplete _level: Incomplete - def __init__(self, verbose: Optional[Union[bool, str, int]] = None) -> None: ... - def __enter__(self) -> None: ... - def __exit__(self, *args) -> None: ... + def __init__(self, verbose: Optional[Union[bool, str, int]]=None) -> None: + ... + + def __enter__(self) -> None: + ... -logger: Incomplete + def __exit__(self, *args) -> None: + ... +logger: Incomplete \ No newline at end of file diff --git a/pycrostates/utils/mixin.pyi b/pycrostates/utils/mixin.pyi index b0198197..c2f8716d 100644 --- a/pycrostates/utils/mixin.pyi +++ b/pycrostates/utils/mixin.pyi @@ -7,10 +7,14 @@ class ChannelsMixin: """Channels Mixin for futur implementation.""" class ContainsMixin(MNEContainsMixin): - def __contains__(self, ch_type) -> bool: ... + + def __contains__(self, ch_type) -> bool: + ... + def __getattribute__(self, name): """Attribute getter.""" class MontageMixin(MNEMontageMixin): + def __getattribute__(self, name): - """Attribute getter.""" + """Attribute getter.""" \ No newline at end of file diff --git a/pycrostates/utils/sys_info.pyi b/pycrostates/utils/sys_info.pyi index 7b9374a5..bd53f3af 100644 --- a/pycrostates/utils/sys_info.pyi +++ b/pycrostates/utils/sys_info.pyi @@ -4,7 +4,7 @@ from packaging.requirements import Requirement from ._checks import _check_type as _check_type -def sys_info(fid: Optional[IO] = None, developer: bool = False): +def sys_info(fid: Optional[IO]=None, developer: bool=False): """Print the system information for debugging. Parameters @@ -16,7 +16,5 @@ def sys_info(fid: Optional[IO] = None, developer: bool = False): If True, display information about optional dependencies. """ -def _list_dependencies_info( - out: Callable, ljust: int, package: str, dependencies: list[Requirement] -): - """List dependencies names and versions.""" +def _list_dependencies_info(out: Callable, ljust: int, package: str, dependencies: list[Requirement]): + """List dependencies names and versions.""" \ No newline at end of file diff --git a/pycrostates/utils/utils.pyi b/pycrostates/utils/utils.pyi index 24ca6443..3c34489a 100644 --- a/pycrostates/utils/utils.pyi +++ b/pycrostates/utils/utils.pyi @@ -2,7 +2,7 @@ from _typeshed import Incomplete from ._logs import logger as logger -def _corr_vectors(A, B, axis: int = 0): +def _corr_vectors(A, B, axis: int=0): """Compute pairwise correlation of multiple pairs of vectors. Fast way to compute correlation of multiple pairs of vectors without computing all @@ -26,8 +26,8 @@ def _corr_vectors(A, B, axis: int = 0): For each pair of vectors, the correlation between them. """ -def _distance_matrix(X, Y: Incomplete | None = None): +def _distance_matrix(X, Y: Incomplete | None=None): """Distance matrix used in metrics.""" def _compare_infos(cluster_info, inst_info): - """Check that channels in cluster_info are all present in inst_info.""" + """Check that channels in cluster_info are all present in inst_info.""" \ No newline at end of file diff --git a/pycrostates/viz/cluster_centers.pyi b/pycrostates/viz/cluster_centers.pyi index 40d64698..96324034 100644 --- a/pycrostates/viz/cluster_centers.pyi +++ b/pycrostates/viz/cluster_centers.pyi @@ -12,18 +12,7 @@ from ..utils._logs import logger as logger _GRADIENT_KWARGS_DEFAULTS: dict[str, str] -def plot_cluster_centers( - cluster_centers: NDArray[float], - info: Union[Info, CHInfo], - cluster_names: list[str] = None, - axes: Optional[Union[Axes, NDArray[Axes]]] = None, - show_gradient: Optional[bool] = False, - gradient_kwargs: dict[str, Any] = ..., - *, - block: bool = False, - verbose: Optional[str] = None, - **kwargs, -): +def plot_cluster_centers(cluster_centers: NDArray[float], info: Union[Info, CHInfo], cluster_names: list[str]=None, axes: Optional[Union[Axes, NDArray[Axes]]]=None, show_gradient: Optional[bool]=False, gradient_kwargs: dict[str, Any]=..., *, block: bool=False, verbose: Optional[str]=None, **kwargs): """Create topographic maps for cluster centers. Parameters @@ -57,4 +46,4 @@ def plot_cluster_centers( ------- fig : Figure Matplotlib figure(s) on which topographic maps are plotted. - """ + """ \ No newline at end of file diff --git a/pycrostates/viz/segmentation.pyi b/pycrostates/viz/segmentation.pyi index 7790751d..466ada87 100644 --- a/pycrostates/viz/segmentation.pyi +++ b/pycrostates/viz/segmentation.pyi @@ -10,21 +10,7 @@ from ..utils._checks import _check_type as _check_type from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def plot_raw_segmentation( - labels: NDArray[int], - raw: BaseRaw, - n_clusters: int, - cluster_names: list[str] = None, - tmin: Optional[Union[int, float]] = None, - tmax: Optional[Union[int, float]] = None, - cmap: Optional[str] = None, - axes: Optional[Axes] = None, - cbar_axes: Optional[Axes] = None, - *, - block: bool = False, - verbose: Optional[str] = None, - **kwargs, -): +def plot_raw_segmentation(labels: NDArray[int], raw: BaseRaw, n_clusters: int, cluster_names: list[str]=None, tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Optional[str]=None, **kwargs): """Plot raw segmentation. Parameters @@ -65,19 +51,7 @@ def plot_raw_segmentation( Matplotlib figure(s) on which topographic maps are plotted. """ -def plot_epoch_segmentation( - labels: NDArray[int], - epochs: BaseEpochs, - n_clusters: int, - cluster_names: list[str] = None, - cmap: Optional[str] = None, - axes: Optional[Axes] = None, - cbar_axes: Optional[Axes] = None, - *, - block: bool = False, - verbose: Optional[str] = None, - **kwargs, -): +def plot_epoch_segmentation(labels: NDArray[int], epochs: BaseEpochs, n_clusters: int, cluster_names: list[str]=None, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Optional[str]=None, **kwargs): """ Plot epochs segmentation. @@ -115,19 +89,7 @@ def plot_epoch_segmentation( Matplotlib figure on which topographic maps are plotted. """ -def _plot_segmentation( - labels: NDArray[int], - gfp: NDArray[float], - times: NDArray[float], - n_clusters: int, - cluster_names: list[str] = None, - cmap: Optional[Union[str, colors.Colormap]] = None, - axes: Optional[Axes] = None, - cbar_axes: Optional[Axes] = None, - *, - verbose: Optional[str] = None, - **kwargs, -): +def _plot_segmentation(labels: NDArray[int], gfp: NDArray[float], times: NDArray[float], n_clusters: int, cluster_names: list[str]=None, cmap: Optional[Union[str, colors.Colormap]]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, verbose: Optional[str]=None, **kwargs): """Code snippet to plot segmentation for raw and epochs.""" def _compatibility_cmap(cmap: Optional[Union[str, colors.Colormap]], n_colors: int): @@ -135,4 +97,4 @@ def _compatibility_cmap(cmap: Optional[Union[str, colors.Colormap]], n_colors: i Matplotlib 3.6 introduced a deprecation of plt.cm.get_cmap(). When support for the 3.6 version is dropped, this checker can be removed. - """ + """ \ No newline at end of file From b1d7996d3b90bb63eebae21329530dc28bae3899 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Wed, 17 Jan 2024 11:11:03 +0100 Subject: [PATCH 19/26] Fix format of stubs file (#148) * fix format of stubs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * try config to run stubs generation on PR * remove push on main to avoid spamming the main branch on every change * fix condition --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/stubs.yaml | 2 + pycrostates/_typing.pyi | 3 +- pycrostates/cluster/_base.pyi | 116 +++++++++++++++--- pycrostates/cluster/aahc.pyi | 38 ++++-- pycrostates/cluster/kmeans.pyi | 48 ++++++-- pycrostates/cluster/utils/utils.pyi | 10 +- pycrostates/datasets/lemon/lemon.pyi | 2 +- pycrostates/io/ch_data.pyi | 45 ++++--- pycrostates/io/fiff.pyi | 40 +++++- pycrostates/io/meas_info.pyi | 19 ++- pycrostates/io/reader.pyi | 2 +- pycrostates/metrics/calinski_harabasz.pyi | 2 +- pycrostates/metrics/davies_bouldin.pyi | 2 +- pycrostates/metrics/dunn.pyi | 7 +- pycrostates/metrics/silhouette.pyi | 2 +- .../preprocessing/extract_gfp_peaks.pyi | 19 ++- pycrostates/preprocessing/resample.pyi | 32 +++-- pycrostates/preprocessing/spatial_filter.pyi | 13 +- pycrostates/segmentation/_base.pyi | 42 ++++--- pycrostates/segmentation/segmentation.pyi | 32 +++-- pycrostates/segmentation/transitions.pyi | 34 +++-- pycrostates/utils/_checks.pyi | 22 ++-- pycrostates/utils/_config.pyi | 3 +- pycrostates/utils/_docs.pyi | 2 +- pycrostates/utils/_fixes.pyi | 3 +- pycrostates/utils/_imports.pyi | 4 +- pycrostates/utils/_logs.pyi | 32 ++--- pycrostates/utils/mixin.pyi | 8 +- pycrostates/utils/sys_info.pyi | 8 +- pycrostates/utils/utils.pyi | 6 +- pycrostates/viz/cluster_centers.pyi | 15 ++- pycrostates/viz/segmentation.pyi | 46 ++++++- pyproject.toml | 9 +- 33 files changed, 476 insertions(+), 192 deletions(-) diff --git a/.github/workflows/stubs.yaml b/.github/workflows/stubs.yaml index 90545bf2..890461d2 100644 --- a/.github/workflows/stubs.yaml +++ b/.github/workflows/stubs.yaml @@ -3,6 +3,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} cancel-in-progress: true on: # yamllint disable-line rule:truthy + pull_request: schedule: - cron: '0 3 * * *' workflow_dispatch: @@ -31,6 +32,7 @@ jobs: - name: Generate stub files run: python tools/stubgen.py - name: Push stub files + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' run: | git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' diff --git a/pycrostates/_typing.pyi b/pycrostates/_typing.pyi index 52aeca59..7417a49c 100644 --- a/pycrostates/_typing.pyi +++ b/pycrostates/_typing.pyi @@ -15,5 +15,6 @@ class Cluster(ABC): class Segmentation(ABC): """Typing for a clustering class.""" + RANDomState = Optional[Union[int, RandomState, Generator]] -Picks = Optional[Union[str, NDArray[int]]] \ No newline at end of file +Picks = Optional[Union[str, NDArray[int]]] diff --git a/pycrostates/cluster/_base.pyi b/pycrostates/cluster/_base.pyi index 8ca0bbbc..ee4bf7f0 100644 --- a/pycrostates/cluster/_base.pyi +++ b/pycrostates/cluster/_base.pyi @@ -28,6 +28,7 @@ from .utils import optimize_order as optimize_order class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): """Base Class for Microstates Clustering algorithms.""" + _n_clusters: Incomplete _cluster_names: Incomplete _cluster_centers_: Incomplete @@ -38,13 +39,11 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): _fitted: bool @abstractmethod - def __init__(self): - ... - + def __init__(self): ... def __repr__(self) -> str: """String representation.""" - def _repr_html_(self, caption: Incomplete | None=None): + def _repr_html_(self, caption: Incomplete | None = None): """HTML representation.""" def __eq__(self, other: Any) -> bool: @@ -53,7 +52,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): def __ne__(self, other: Any) -> bool: """Different != method.""" - def copy(self, deep: bool=True): + def copy(self, deep: bool = True): """Return a copy of the instance. Parameters @@ -69,7 +68,16 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): """Check if the cluster is unfitted.""" @abstractmethod - def fit(self, inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks='eeg', tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, reject_by_annotation: bool=True, *, verbose: Optional[str]=None) -> NDArray[float]: + def fit( + self, + inst: Union[BaseRaw, BaseEpochs, CHData], + picks: Picks = "eeg", + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + reject_by_annotation: bool = True, + *, + verbose: Optional[str] = None, + ) -> NDArray[float]: """Compute cluster centers. Parameters @@ -93,7 +101,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -102,7 +110,11 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): ``"WARNING"`` for False and to ``"INFO"`` for True. """ - def rename_clusters(self, mapping: Optional[dict[str, str]]=None, new_names: Optional[Union[list[str], tuple[str, ...]]]=None) -> None: + def rename_clusters( + self, + mapping: Optional[dict[str, str]] = None, + new_names: Optional[Union[list[str], tuple[str, ...]]] = None, + ) -> None: """Rename the clusters. Parameters @@ -119,7 +131,12 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Operates in-place. """ - def reorder_clusters(self, mapping: Optional[dict[int, int]]=None, order: Optional[Union[list[int], tuple[int, ...], NDArray[int]]]=None, template: Optional[Cluster]=None) -> None: + def reorder_clusters( + self, + mapping: Optional[dict[int, int]] = None, + order: Optional[Union[list[int], tuple[int, ...], NDArray[int]]] = None, + template: Optional[Cluster] = None, + ) -> None: """ Reorder the clusters of the fitted model. @@ -149,7 +166,9 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Operates in-place. """ - def invert_polarity(self, invert: Union[bool, list[bool], tuple[bool, ...], NDArray[bool]]) -> None: + def invert_polarity( + self, invert: Union[bool, list[bool], tuple[bool, ...], NDArray[bool]] + ) -> None: """Invert map polarities. Parameters @@ -169,7 +188,20 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): an article). """ - def plot(self, axes: Optional[Union[Axes, NDArray[Axes]]]=None, show_gradient: Optional[bool]=False, gradient_kwargs: dict[str, Any]={'color': 'black', 'linestyle': '-', 'marker': 'P'}, *, block: bool=False, verbose: Optional[str]=None, **kwargs): + def plot( + self, + axes: Optional[Union[Axes, NDArray[Axes]]] = None, + show_gradient: Optional[bool] = False, + gradient_kwargs: dict[str, Any] = { + "color": "black", + "linestyle": "-", + "marker": "P", + }, + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, + ): """ Plot cluster centers as topographic maps. @@ -211,7 +243,19 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Path to the ``.fif`` file where the clustering solution is saved. """ - def predict(self, inst: Union[BaseRaw, BaseEpochs], picks: Picks=None, factor: int=0, half_window_size: int=1, tol: Union[int, float]=1e-05, min_segment_length: int=0, reject_edges: bool=True, reject_by_annotation: bool=True, *, verbose: Optional[str]=None): + def predict( + self, + inst: Union[BaseRaw, BaseEpochs], + picks: Picks = None, + factor: int = 0, + half_window_size: int = 1, + tol: Union[int, float] = 1e-05, + min_segment_length: int = 0, + reject_edges: bool = True, + reject_by_annotation: bool = True, + *, + verbose: Optional[str] = None, + ): """Segment `~mne.io.Raw` or `~mne.Epochs` into microstate sequence. Segment instance into microstate sequence using the segmentation smoothing @@ -249,7 +293,7 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -269,18 +313,50 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): .. footbibliography:: """ - def _predict_raw(self, raw: BaseRaw, picks_data: NDArray[int], factor: int, tol: Union[int, float], half_window_size: int, min_segment_length: int, reject_edges: bool, reject_by_annotation: bool) -> RawSegmentation: + def _predict_raw( + self, + raw: BaseRaw, + picks_data: NDArray[int], + factor: int, + tol: Union[int, float], + half_window_size: int, + min_segment_length: int, + reject_edges: bool, + reject_by_annotation: bool, + ) -> RawSegmentation: """Create segmentation for raw.""" - def _predict_epochs(self, epochs: BaseEpochs, picks_data: NDArray[int], factor: int, tol: Union[int, float], half_window_size: int, min_segment_length: int, reject_edges: bool) -> EpochsSegmentation: + def _predict_epochs( + self, + epochs: BaseEpochs, + picks_data: NDArray[int], + factor: int, + tol: Union[int, float], + half_window_size: int, + min_segment_length: int, + reject_edges: bool, + ) -> EpochsSegmentation: """Create segmentation for epochs.""" @staticmethod - def _segment(data: NDArray[float], states: NDArray[float], factor: int, tol: Union[int, float], half_window_size: int) -> NDArray[int]: + def _segment( + data: NDArray[float], + states: NDArray[float], + factor: int, + tol: Union[int, float], + half_window_size: int, + ) -> NDArray[int]: """Create segmentation. Must operate on a copy of states.""" @staticmethod - def _smooth_segmentation(data: NDArray[float], states: NDArray[float], labels: NDArray[int], factor: int, tol: Union[int, float], half_window_size: int) -> NDArray[int]: + def _smooth_segmentation( + data: NDArray[float], + states: NDArray[float], + labels: NDArray[int], + factor: int, + tol: Union[int, float], + half_window_size: int, + ) -> NDArray[int]: """Apply smoothing. Adapted from [1]. @@ -296,7 +372,9 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): """ @staticmethod - def _reject_short_segments(segmentation: NDArray[int], data: NDArray[float], min_segment_length: int) -> NDArray[int]: + def _reject_short_segments( + segmentation: NDArray[int], data: NDArray[float], min_segment_length: int + ) -> NDArray[int]: """Reject segments that are too short. Reject segments that are too short by replacing the labels with the adjacent @@ -374,4 +452,4 @@ class _BaseCluster(Cluster, ChannelsMixin, ContainsMixin, MontageMixin): @staticmethod def _check_n_clusters(n_clusters: int) -> int: - """Check that the number of clusters is a positive integer.""" \ No newline at end of file + """Check that the number of clusters is a positive integer.""" diff --git a/pycrostates/cluster/aahc.pyi b/pycrostates/cluster/aahc.pyi index 79d06077..72f416f0 100644 --- a/pycrostates/cluster/aahc.pyi +++ b/pycrostates/cluster/aahc.pyi @@ -30,18 +30,15 @@ class AAHCluster(_BaseCluster): ---------- .. footbibliography:: """ + _n_clusters: Incomplete _cluster_names: Incomplete _ignore_polarity: bool _normalize_input: Incomplete _GEV_: Incomplete - def __init__(self, n_clusters: int, normalize_input: bool=False) -> None: - ... - - def _repr_html_(self, caption: Incomplete | None=None): - ... - + def __init__(self, n_clusters: int, normalize_input: bool = False) -> None: ... + def _repr_html_(self, caption: Incomplete | None = None): ... def __eq__(self, other: Any) -> bool: """Equality == method.""" @@ -54,7 +51,16 @@ class AAHCluster(_BaseCluster): _labels_: Incomplete _fitted: bool - def fit(self, inst: Union[BaseRaw, BaseEpochs], picks: Picks='eeg', tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, reject_by_annotation: bool=True, *, verbose: Optional[str]=None) -> None: + def fit( + self, + inst: Union[BaseRaw, BaseEpochs], + picks: Picks = "eeg", + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + reject_by_annotation: bool = True, + *, + verbose: Optional[str] = None, + ) -> None: """Compute cluster centers. Parameters @@ -78,7 +84,7 @@ class AAHCluster(_BaseCluster): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -97,11 +103,21 @@ class AAHCluster(_BaseCluster): """ @staticmethod - def _aahc(data: NDArray[float], n_clusters: int, ignore_polarity: bool, normalize_input: bool) -> tuple[float, NDArray[float], NDArray[int]]: + def _aahc( + data: NDArray[float], + n_clusters: int, + ignore_polarity: bool, + normalize_input: bool, + ) -> tuple[float, NDArray[float], NDArray[int]]: """Run the AAHC algorithm.""" @staticmethod - def _compute_maps(data: NDArray[float], n_clusters: int, ignore_polarity: bool, normalize_input: bool) -> tuple[NDArray[float], NDArray[int]]: + def _compute_maps( + data: NDArray[float], + n_clusters: int, + ignore_polarity: bool, + normalize_input: bool, + ) -> tuple[NDArray[float], NDArray[int]]: """Compute microstates maps.""" @property @@ -131,4 +147,4 @@ class AAHCluster(_BaseCluster): @staticmethod def _check_normalize_input(normalize_input: bool) -> bool: - """Check that normalize_input is a boolean.""" \ No newline at end of file + """Check that normalize_input is a boolean.""" diff --git a/pycrostates/cluster/kmeans.pyi b/pycrostates/cluster/kmeans.pyi index 6b65f95c..8d173412 100644 --- a/pycrostates/cluster/kmeans.pyi +++ b/pycrostates/cluster/kmeans.pyi @@ -49,6 +49,7 @@ class ModKMeans(_BaseCluster): ---------- .. footbibliography:: """ + _n_clusters: Incomplete _cluster_names: Incomplete _n_init: Incomplete @@ -57,12 +58,15 @@ class ModKMeans(_BaseCluster): _random_state: Incomplete _GEV_: Incomplete - def __init__(self, n_clusters: int, n_init: int=100, max_iter: int=300, tol: Union[int, float]=1e-06, random_state: RANDomState=None) -> None: - ... - - def _repr_html_(self, caption: Incomplete | None=None): - ... - + def __init__( + self, + n_clusters: int, + n_init: int = 100, + max_iter: int = 300, + tol: Union[int, float] = 1e-06, + random_state: RANDomState = None, + ) -> None: ... + def _repr_html_(self, caption: Incomplete | None = None): ... def __eq__(self, other: Any) -> bool: """Equality == method.""" @@ -76,7 +80,17 @@ class ModKMeans(_BaseCluster): _fitted: bool _ignore_polarity: bool - def fit(self, inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks='eeg', tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, reject_by_annotation: bool=True, n_jobs: int=1, *, verbose: Optional[str]=None) -> None: + def fit( + self, + inst: Union[BaseRaw, BaseEpochs, CHData], + picks: Picks = "eeg", + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + reject_by_annotation: bool = True, + n_jobs: int = 1, + *, + verbose: Optional[str] = None, + ) -> None: """Compute cluster centers. Parameters @@ -100,7 +114,7 @@ class ModKMeans(_BaseCluster): Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. n_jobs : int | None The number of jobs to run in parallel. If ``-1``, it is set @@ -126,11 +140,23 @@ class ModKMeans(_BaseCluster): """ @staticmethod - def _kmeans(data: NDArray[float], n_clusters: int, max_iter: int, random_state: Union[RandomState, Generator], tol: Union[int, float]) -> tuple[float, NDArray[float], NDArray[int], bool]: + def _kmeans( + data: NDArray[float], + n_clusters: int, + max_iter: int, + random_state: Union[RandomState, Generator], + tol: Union[int, float], + ) -> tuple[float, NDArray[float], NDArray[int], bool]: """Run the k-means algorithm.""" @staticmethod - def _compute_maps(data: NDArray[float], n_clusters: int, max_iter: int, random_state: Union[RandomState, Generator], tol: Union[int, float]) -> tuple[NDArray[float], bool]: + def _compute_maps( + data: NDArray[float], + n_clusters: int, + max_iter: int, + random_state: Union[RandomState, Generator], + tol: Union[int, float], + ) -> tuple[NDArray[float], bool]: """Compute microstates maps. Based on mne_microstates by Marijn van Vliet @@ -189,4 +215,4 @@ class ModKMeans(_BaseCluster): @staticmethod def _check_tol(tol: Union[int, float]) -> Union[int, float]: - """Check that tol is a positive number.""" \ No newline at end of file + """Check that tol is a positive number.""" diff --git a/pycrostates/cluster/utils/utils.pyi b/pycrostates/cluster/utils/utils.pyi index c15ab1c7..44a00fd8 100644 --- a/pycrostates/cluster/utils/utils.pyi +++ b/pycrostates/cluster/utils/utils.pyi @@ -4,9 +4,11 @@ from ..._typing import Cluster as Cluster from ...utils._checks import _check_type as _check_type from ...utils._docs import fill_doc as fill_doc -def _optimize_order(centers: NDArray[float], template_centers: NDArray[float], ignore_polarity: bool=True): - ... - +def _optimize_order( + centers: NDArray[float], + template_centers: NDArray[float], + ignore_polarity: bool = True, +): ... def optimize_order(inst: Cluster, template_inst: Cluster): """Optimize the order of cluster centers between two cluster instances. @@ -26,4 +28,4 @@ def optimize_order(inst: Cluster, template_inst: Cluster): ------- order : list of int The new order to apply to inst to maximize auto-correlation of cluster centers. - """ \ No newline at end of file + """ diff --git a/pycrostates/datasets/lemon/lemon.pyi b/pycrostates/datasets/lemon/lemon.pyi index 61ba39ca..c9be379d 100644 --- a/pycrostates/datasets/lemon/lemon.pyi +++ b/pycrostates/datasets/lemon/lemon.pyi @@ -68,4 +68,4 @@ def standardize(raw: BaseRaw): If you don't want to interpolate missing channels, you can use :func:`mne.channels.equalize_channels` instead to have the same electrodes across different recordings. - """ \ No newline at end of file + """ diff --git a/pycrostates/io/ch_data.pyi b/pycrostates/io/ch_data.pyi index d920412f..d1dd8332 100644 --- a/pycrostates/io/ch_data.pyi +++ b/pycrostates/io/ch_data.pyi @@ -27,16 +27,15 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): Atemporal measurement information. If a `mne.Info` is provided, it is converted to a `~pycrostates.io.ChInfo`. """ + _data: Incomplete _info: Incomplete - def __init__(self, data: NDArray[float], info: Union[Info, CHInfo]) -> None: - ... - + def __init__(self, data: NDArray[float], info: Union[Info, CHInfo]) -> None: ... def __repr__(self) -> str: """String representation.""" - def _repr_html_(self, caption: Incomplete | None=None): + def _repr_html_(self, caption: Incomplete | None = None): """HTML representation.""" def __eq__(self, other: Any) -> bool: @@ -45,7 +44,7 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): def __ne__(self, other: Any) -> bool: """Different != method.""" - def copy(self, deep: bool=True): + def copy(self, deep: bool = True): """Return a copy of the instance. Parameters @@ -54,19 +53,19 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): If True, `~copy.deepcopy` is used instead of `~copy.copy`. """ - def get_data(self, picks: Incomplete | None=None) -> NDArray[float]: + def get_data(self, picks: Incomplete | None = None) -> NDArray[float]: """Retrieve the data array. Parameters ---------- picks : str | array-like | slice | None - Channels to include. Slices and lists of integers will be interpreted as - channel indices. In lists, channel *type* strings (e.g., ``['meg', - 'eeg']``) will pick channels of those types, channel *name* strings (e.g., - ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the - string values "all" to pick all channels, or "data" to pick :term:`data - channels`. None (default) will pick all channels. Note that channels in - ``info['bads']`` *will be included* if their names or indices are + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are explicitly provided. Returns @@ -75,19 +74,19 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): Data array of shape ``(n_channels, n_samples)``. """ - def pick(self, picks, exclude: str='bads'): + def pick(self, picks, exclude: str = "bads"): """Pick a subset of channels. Parameters ---------- picks : str | array-like | slice | None - Channels to include. Slices and lists of integers will be interpreted as - channel indices. In lists, channel *type* strings (e.g., ``['meg', - 'eeg']``) will pick channels of those types, channel *name* strings (e.g., - ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the - string values "all" to pick all channels, or "data" to pick :term:`data - channels`. None (default) will pick all channels. Note that channels in - ``info['bads']`` *will be included* if their names or indices are + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are explicitly provided. exclude : list | str Set of channels to exclude, only used when picking based on types (e.g., @@ -99,7 +98,7 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): The instance modified in-place. """ - def _get_channel_positions(self, picks: Incomplete | None=None): + def _get_channel_positions(self, picks: Incomplete | None = None): """Get channel locations from info. Parameters @@ -126,4 +125,4 @@ class ChData(CHData, ChannelsMixin, ContainsMixin, MontageMixin): @property def preload(self): - """Preload required by some MNE functions.""" \ No newline at end of file + """Preload required by some MNE functions.""" diff --git a/pycrostates/io/fiff.pyi b/pycrostates/io/fiff.pyi index bc7bee9e..b3bacbaf 100644 --- a/pycrostates/io/fiff.pyi +++ b/pycrostates/io/fiff.pyi @@ -13,7 +13,16 @@ from ..utils._checks import _check_value as _check_value from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def _write_cluster(fname: Union[str, Path], cluster_centers_: NDArray[float], chinfo: Union[CHInfo, Info], algorithm: str, cluster_names: list[str], fitted_data: NDArray[float], labels_: NDArray[int], **kwargs): +def _write_cluster( + fname: Union[str, Path], + cluster_centers_: NDArray[float], + chinfo: Union[CHInfo, Info], + algorithm: str, + cluster_names: list[str], + fitted_data: NDArray[float], + labels_: NDArray[int], + **kwargs, +): """Save clustering solution to disk. Parameters @@ -57,10 +66,29 @@ def _read_cluster(fname: Union[str, Path]): def _check_fit_parameters_and_variables(fit_parameters: dict, fit_variables: dict): """Check that we have all the keys we are looking for and return algo.""" -def _create_ModKMeans(cluster_centers_: NDArray[float], info: CHInfo, cluster_names: list[str], fitted_data: NDArray[float], labels_: NDArray[int], n_init: int, max_iter: int, tol: Union[int, float], GEV_: float): +def _create_ModKMeans( + cluster_centers_: NDArray[float], + info: CHInfo, + cluster_names: list[str], + fitted_data: NDArray[float], + labels_: NDArray[int], + n_init: int, + max_iter: int, + tol: Union[int, float], + GEV_: float, +): """Create a ModKMeans cluster.""" -def _create_AAHCluster(cluster_centers_: NDArray[float], info: CHInfo, cluster_names: list[str], fitted_data: NDArray[float], labels_: NDArray[int], ignore_polarity: bool, normalize_input: bool, GEV_: float): +def _create_AAHCluster( + cluster_centers_: NDArray[float], + info: CHInfo, + cluster_names: list[str], + fitted_data: NDArray[float], + labels_: NDArray[int], + ignore_polarity: bool, + normalize_input: bool, + GEV_: float, +): """Create a AAHCluster object.""" def _write_meas_info(fid, info: CHInfo): @@ -90,8 +118,8 @@ def _read_meas_info(fid, tree): Channel information instance. """ -def _serialize(dict_: dict, outer_sep: str=';', inner_sep: str=':'): +def _serialize(dict_: dict, outer_sep: str = ";", inner_sep: str = ":"): """Aux function.""" -def _deserialize(str_: str, outer_sep: str=';', inner_sep: str=':'): - """Aux Function.""" \ No newline at end of file +def _deserialize(str_: str, outer_sep: str = ";", inner_sep: str = ":"): + """Aux Function.""" diff --git a/pycrostates/io/meas_info.pyi b/pycrostates/io/meas_info.pyi index 2a9a9543..73b9834b 100644 --- a/pycrostates/io/meas_info.pyi +++ b/pycrostates/io/meas_info.pyi @@ -130,14 +130,21 @@ class ChInfo(CHInfo, Info): The coordinate frame used, e.g. ``FIFFV_COORD_HEAD``. """ - def __init__(self, info: Optional[Info]=None, ch_names: Optional[Union[int, list[str], tuple[str, ...]]]=None, ch_types: Optional[Union[str, list[str], tuple[str, ...]]]=None) -> None: - ... - + def __init__( + self, + info: Optional[Info] = None, + ch_names: Optional[Union[int, list[str], tuple[str, ...]]] = None, + ch_types: Optional[Union[str, list[str], tuple[str, ...]]] = None, + ) -> None: ... def _init_from_info(self, info: Info): """Init instance from mne Info.""" _unlocked: bool - def _init_from_channels(self, ch_names: Union[int, list[str], tuple[str, ...]], ch_types: Union[str, list[str], tuple[str, ...]]): + def _init_from_channels( + self, + ch_names: Union[int, list[str], tuple[str, ...]], + ch_types: Union[str, list[str], tuple[str, ...]], + ): """Init instance from channel names and types.""" def __getattribute__(self, name): @@ -152,5 +159,5 @@ class ChInfo(CHInfo, Info): def __deepcopy__(self, memodict): """Make a deepcopy.""" - def _check_consistency(self, prepend_error: str=''): - """Do some self-consistency checks and datatype tweaks.""" \ No newline at end of file + def _check_consistency(self, prepend_error: str = ""): + """Do some self-consistency checks and datatype tweaks.""" diff --git a/pycrostates/io/reader.pyi b/pycrostates/io/reader.pyi index 029be484..b39bab4b 100644 --- a/pycrostates/io/reader.pyi +++ b/pycrostates/io/reader.pyi @@ -17,4 +17,4 @@ def read_cluster(fname: Union[str, Path]): ------- cluster : :ref:`Clustering` Fitted clustering instance. - """ \ No newline at end of file + """ diff --git a/pycrostates/metrics/calinski_harabasz.pyi b/pycrostates/metrics/calinski_harabasz.pyi index f7aa9ea6..1e3c5cd4 100644 --- a/pycrostates/metrics/calinski_harabasz.pyi +++ b/pycrostates/metrics/calinski_harabasz.pyi @@ -29,4 +29,4 @@ def calinski_harabasz_score(cluster): References ---------- .. footbibliography:: - """ \ No newline at end of file + """ diff --git a/pycrostates/metrics/davies_bouldin.pyi b/pycrostates/metrics/davies_bouldin.pyi index b229521f..ee1cb124 100644 --- a/pycrostates/metrics/davies_bouldin.pyi +++ b/pycrostates/metrics/davies_bouldin.pyi @@ -49,4 +49,4 @@ def _davies_bouldin_score(X, labels): ------- score: float The resulting Davies-Bouldin score. - """ \ No newline at end of file + """ diff --git a/pycrostates/metrics/dunn.pyi b/pycrostates/metrics/dunn.pyi index 20697f85..a55a740f 100644 --- a/pycrostates/metrics/dunn.pyi +++ b/pycrostates/metrics/dunn.pyi @@ -45,8 +45,5 @@ def _dunn_score(X, labels): Based on https://github.com/jqmviegas/jqm_cvi """ -def _delta_fast(ck, cl, distances): - ... - -def _big_delta_fast(ci, distances): - ... \ No newline at end of file +def _delta_fast(ck, cl, distances): ... +def _big_delta_fast(ci, distances): ... diff --git a/pycrostates/metrics/silhouette.pyi b/pycrostates/metrics/silhouette.pyi index 0c5b6ee4..092ab805 100644 --- a/pycrostates/metrics/silhouette.pyi +++ b/pycrostates/metrics/silhouette.pyi @@ -31,4 +31,4 @@ def silhouette_score(cluster): References ---------- .. footbibliography:: - """ \ No newline at end of file + """ diff --git a/pycrostates/preprocessing/extract_gfp_peaks.pyi b/pycrostates/preprocessing/extract_gfp_peaks.pyi index 29aa19b1..b7ac9075 100644 --- a/pycrostates/preprocessing/extract_gfp_peaks.pyi +++ b/pycrostates/preprocessing/extract_gfp_peaks.pyi @@ -14,7 +14,16 @@ from ..utils._checks import _check_type as _check_type from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def extract_gfp_peaks(inst: Union[BaseRaw, BaseEpochs], picks: Picks='eeg', return_all: bool=False, min_peak_distance: int=1, tmin: Optional[float]=None, tmax: Optional[float]=None, reject_by_annotation: bool=True, verbose: Incomplete | None=None) -> CHData: +def extract_gfp_peaks( + inst: Union[BaseRaw, BaseEpochs], + picks: Picks = "eeg", + return_all: bool = False, + min_peak_distance: int = 1, + tmin: Optional[float] = None, + tmax: Optional[float] = None, + reject_by_annotation: bool = True, + verbose: Incomplete | None = None, +) -> CHData: """:term:`Global Field Power` (:term:`GFP`) peaks extraction. Extract :term:`Global Field Power` (:term:`GFP`) peaks from :class:`~mne.Epochs` or @@ -48,7 +57,7 @@ def extract_gfp_peaks(inst: Union[BaseRaw, BaseEpochs], picks: Picks='eeg', retu Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. verbose : int | str | bool | None Sets the verbosity level. The verbosity increases gradually between ``"CRITICAL"``, @@ -69,7 +78,9 @@ def extract_gfp_peaks(inst: Union[BaseRaw, BaseEpochs], picks: Picks='eeg', retu default values. """ -def _extract_gfp_peaks(data: NDArray[float], min_peak_distance: int=2) -> NDArray[float]: +def _extract_gfp_peaks( + data: NDArray[float], min_peak_distance: int = 2 +) -> NDArray[float]: """Extract GFP peaks from input data. Parameters @@ -85,4 +96,4 @@ def _extract_gfp_peaks(data: NDArray[float], min_peak_distance: int=2) -> NDArra ------- peaks : array of shape (n_picks,) The indices when peaks occur. - """ \ No newline at end of file + """ diff --git a/pycrostates/preprocessing/resample.pyi b/pycrostates/preprocessing/resample.pyi index 11e3762c..d68f3146 100644 --- a/pycrostates/preprocessing/resample.pyi +++ b/pycrostates/preprocessing/resample.pyi @@ -14,7 +14,19 @@ from ..utils._checks import _check_type as _check_type from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def resample(inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks=None, tmin: Optional[float]=None, tmax: Optional[float]=None, reject_by_annotation: bool=True, n_resamples: int=None, n_samples: int=None, coverage: float=None, replace: bool=True, random_state: RANDomState=None, verbose: Incomplete | None=None) -> list[CHData]: +def resample( + inst: Union[BaseRaw, BaseEpochs, CHData], + picks: Picks = None, + tmin: Optional[float] = None, + tmax: Optional[float] = None, + reject_by_annotation: bool = True, + n_resamples: int = None, + n_samples: int = None, + coverage: float = None, + replace: bool = True, + random_state: RANDomState = None, + verbose: Incomplete | None = None, +) -> list[CHData]: """Resample a recording into epochs of random samples. Resample :class:`~mne.io.Raw`. :class:`~mne.Epochs` or @@ -26,13 +38,13 @@ def resample(inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks=None, tmin: inst : Raw | Epochs | ChData Instance to resample. picks : str | array-like | slice | None - Channels to include. Slices and lists of integers will be interpreted as - channel indices. In lists, channel *type* strings (e.g., ``['meg', - 'eeg']``) will pick channels of those types, channel *name* strings (e.g., - ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the - string values "all" to pick all channels, or "data" to pick :term:`data - channels`. None (default) will pick all channels. Note that channels in - ``info['bads']`` *will be included* if their names or indices are + Channels to include. Slices and lists of integers will be interpreted as + channel indices. In lists, channel *type* strings (e.g., ``['meg', + 'eeg']``) will pick channels of those types, channel *name* strings (e.g., + ``['MEG0111', 'MEG2623']`` will pick the given channels. Can also be the + string values "all" to pick all channels, or "data" to pick :term:`data + channels`. None (default) will pick all channels. Note that channels in + ``info['bads']`` *will be included* if their names or indices are explicitly provided. tmin : float Start time of the raw data to use in seconds (must be >= 0). @@ -42,7 +54,7 @@ def resample(inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks=None, tmin: Whether to omit bad segments from the data before fitting. If ``True`` (default), annotated segments whose description begins with ``'bad'`` are omitted. If ``False``, no rejection based on annotations is performed. - + Has no effect if ``inst`` is not a :class:`mne.io.Raw` object. n_resamples : int Number of resamples to draw. Each epoch can be used to fit a separate clustering @@ -76,4 +88,4 @@ def resample(inst: Union[BaseRaw, BaseEpochs, CHData], picks: Picks=None, tmin: ----- Only two of ``n_resamples``, ``n_samples`` and ``coverage`` parameters must be defined, the non-defined one will be determine at runtime by the 2 other parameters. - """ \ No newline at end of file + """ diff --git a/pycrostates/preprocessing/spatial_filter.pyi b/pycrostates/preprocessing/spatial_filter.pyi index ee8f0ba5..e49fe3a0 100644 --- a/pycrostates/preprocessing/spatial_filter.pyi +++ b/pycrostates/preprocessing/spatial_filter.pyi @@ -16,7 +16,15 @@ from ..utils._logs import logger as logger def _check_adjacency(adjacency, info, ch_type): """Check adjacency matrix.""" -def apply_spatial_filter(inst: Union[BaseRaw, BaseEpochs, CHData], ch_type: str='eeg', exclude_bads: bool=True, origin: Union[str, NDArray[float]]='auto', adjacency: Union[csr_matrix, str]='auto', n_jobs: int=1, verbose: Incomplete | None=None): +def apply_spatial_filter( + inst: Union[BaseRaw, BaseEpochs, CHData], + ch_type: str = "eeg", + exclude_bads: bool = True, + origin: Union[str, NDArray[float]] = "auto", + adjacency: Union[csr_matrix, str] = "auto", + n_jobs: int = 1, + verbose: Incomplete | None = None, +): """Apply a spatial filter. Adapted from \\ :footcite:t:`michel2019eeg`. Apply an instantaneous filter which @@ -82,5 +90,4 @@ def apply_spatial_filter(inst: Union[BaseRaw, BaseEpochs, CHData], ch_type: str= .. footbibliography:: """ -def _channel_spatial_filter(index, data, adjacency_vector, interpolate_matrix): - ... \ No newline at end of file +def _channel_spatial_filter(index, data, adjacency_vector, interpolate_matrix): ... diff --git a/pycrostates/segmentation/_base.pyi b/pycrostates/segmentation/_base.pyi index 8342710c..b4387401 100644 --- a/pycrostates/segmentation/_base.pyi +++ b/pycrostates/segmentation/_base.pyi @@ -33,6 +33,7 @@ class _BaseSegmentation(Segmentation): predict_parameters : dict | None The prediction parameters. """ + _labels: Incomplete _inst: Incomplete _cluster_centers_: Incomplete @@ -40,16 +41,17 @@ class _BaseSegmentation(Segmentation): _predict_parameters: Incomplete @abstractmethod - def __init__(self, labels: NDArray[int], inst: Union[BaseRaw, BaseEpochs], cluster_centers_: NDArray[float], cluster_names: Optional[list[str]]=None, predict_parameters: Optional[dict]=None): - ... - - def __repr__(self) -> str: - ... - - def _repr_html_(self, caption: Incomplete | None=None): - ... - - def compute_parameters(self, norm_gfp: bool=True, return_dist: bool=False): + def __init__( + self, + labels: NDArray[int], + inst: Union[BaseRaw, BaseEpochs], + cluster_centers_: NDArray[float], + cluster_names: Optional[list[str]] = None, + predict_parameters: Optional[dict] = None, + ): ... + def __repr__(self) -> str: ... + def _repr_html_(self, caption: Incomplete | None = None): ... + def compute_parameters(self, norm_gfp: bool = True, return_dist: bool = False): """Compute microstate parameters. .. warning:: @@ -95,7 +97,9 @@ class _BaseSegmentation(Segmentation): expressed in seconds (s). """ - def compute_transition_matrix(self, stat: str='probability', ignore_repetitions: bool=True): + def compute_transition_matrix( + self, stat: str = "probability", ignore_repetitions: bool = True + ): """Compute the observed transition matrix. Count the number of transitions from one state to another and aggregate the @@ -119,7 +123,9 @@ class _BaseSegmentation(Segmentation): ``reject_edges`` parameter to ``True`` when predicting the segmentation. """ - def compute_expected_transition_matrix(self, stat: str='probability', ignore_repetitions: bool=True): + def compute_expected_transition_matrix( + self, stat: str = "probability", ignore_repetitions: bool = True + ): """Compute the expected transition matrix. Compute the theoretical transition matrix as if time course was ignored, but @@ -133,7 +139,7 @@ class _BaseSegmentation(Segmentation): ---------- stat : str Aggregate statistic to compute transitions. Can be: - + * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is @@ -151,7 +157,9 @@ class _BaseSegmentation(Segmentation): First axis indicates state ``"from"``. Second axis indicates state ``"to"``. """ - def plot_cluster_centers(self, axes: Optional[Union[Axes, NDArray[Axes]]]=None, block: bool=False): + def plot_cluster_centers( + self, axes: Optional[Union[Axes, NDArray[Axes]]] = None, block: bool = False + ): """Plot cluster centers as topographic maps. Parameters @@ -170,7 +178,9 @@ class _BaseSegmentation(Segmentation): """ @staticmethod - def _check_cluster_names(cluster_names: list[str], cluster_centers_: NDArray[float]): + def _check_cluster_names( + cluster_names: list[str], cluster_centers_: NDArray[float] + ): """Check that the argument 'cluster_names' is valid.""" @staticmethod @@ -204,4 +214,4 @@ class _BaseSegmentation(Segmentation): """Name of the cluster centers. :type: `list` - """ \ No newline at end of file + """ diff --git a/pycrostates/segmentation/segmentation.pyi b/pycrostates/segmentation/segmentation.pyi index e243079f..d20f8147 100644 --- a/pycrostates/segmentation/segmentation.pyi +++ b/pycrostates/segmentation/segmentation.pyi @@ -29,10 +29,18 @@ class RawSegmentation(_BaseSegmentation): The prediction parameters. """ - def __init__(self, *args, **kwargs) -> None: - ... - - def plot(self, tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Optional[str]=None): + def __init__(self, *args, **kwargs) -> None: ... + def plot( + self, + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Optional[str] = None, + ): """Plot the segmentation. Parameters @@ -84,10 +92,16 @@ class EpochsSegmentation(_BaseSegmentation): The prediction parameters. """ - def __init__(self, *args, **kwargs) -> None: - ... - - def plot(self, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Incomplete | None=None): + def __init__(self, *args, **kwargs) -> None: ... + def plot( + self, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Incomplete | None = None, + ): """Plot segmentation. Parameters @@ -116,4 +130,4 @@ class EpochsSegmentation(_BaseSegmentation): @property def epochs(self) -> BaseEpochs: - """`~mne.Epochs` instance from which the segmentation was computed.""" \ No newline at end of file + """`~mne.Epochs` instance from which the segmentation was computed.""" diff --git a/pycrostates/segmentation/transitions.pyi b/pycrostates/segmentation/transitions.pyi index a14c68f6..8f8ff1bd 100644 --- a/pycrostates/segmentation/transitions.pyi +++ b/pycrostates/segmentation/transitions.pyi @@ -4,7 +4,12 @@ from ..utils._checks import _check_type as _check_type from ..utils._checks import _check_value as _check_value from ..utils._docs import fill_doc as fill_doc -def compute_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: +def compute_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: """Compute the observed transition matrix. Count the number of transitions from one state to another and aggregate the result @@ -18,7 +23,7 @@ def compute_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str=' The number of clusters, i.e. the number of microstates. stat : str Aggregate statistic to compute transitions. Can be: - + * ``count``: show the number of observations of each transition. * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. @@ -37,10 +42,20 @@ def compute_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str=' First axis indicates state ``"from"``. Second axis indicates state ``"to"``. """ -def _compute_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: +def _compute_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: """Compute observed transition.""" -def compute_expected_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: +def compute_expected_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: """Compute the expected transition matrix. Compute the theoretical transition matrix as if time course was ignored, but @@ -58,7 +73,7 @@ def compute_expected_transition_matrix(labels: NDArray[int], n_clusters: int, st The number of clusters, i.e. the number of microstates. stat : str Aggregate statistic to compute transitions. Can be: - + * ``probability`` or ``proportion``: normalize count such as the probabilities along the first axis is always equal to ``1``. * ``percent``: normalize count such as the probabilities along the first axis is @@ -76,11 +91,16 @@ def compute_expected_transition_matrix(labels: NDArray[int], n_clusters: int, st First axis indicates state ``"from"``. Second axis indicates state ``"to"``. """ -def _compute_expected_transition_matrix(labels: NDArray[int], n_clusters: int, stat: str='probability', ignore_repetitions: bool=True) -> NDArray[float]: +def _compute_expected_transition_matrix( + labels: NDArray[int], + n_clusters: int, + stat: str = "probability", + ignore_repetitions: bool = True, +) -> NDArray[float]: """Compute theoretical transition matrix. The theoretical transition matrix takes into account the time coverage. """ def _check_labels_n_clusters(labels: NDArray[int], n_clusters: int) -> None: - """Checker for labels and n_clusters.""" \ No newline at end of file + """Checker for labels and n_clusters.""" diff --git a/pycrostates/utils/_checks.pyi b/pycrostates/utils/_checks.pyi index d40c5740..7afa9228 100644 --- a/pycrostates/utils/_checks.pyi +++ b/pycrostates/utils/_checks.pyi @@ -4,7 +4,7 @@ from _typeshed import Incomplete from ._docs import fill_doc as fill_doc -def _ensure_int(item, item_name: Incomplete | None=None): +def _ensure_int(item, item_name: Incomplete | None = None): """ Ensure a variable is an integer. @@ -22,19 +22,16 @@ def _ensure_int(item, item_name: Incomplete | None=None): """ class _IntLike: - @classmethod - def __instancecheck__(cls, other): - ... + def __instancecheck__(cls, other): ... class _Callable: - @classmethod - def __instancecheck__(cls, other): - ... + def __instancecheck__(cls, other): ... + _types: Incomplete -def _check_type(item, types, item_name: Incomplete | None=None): +def _check_type(item, types, item_name: Incomplete | None = None): """ Check that item is an instance of types. @@ -55,7 +52,12 @@ def _check_type(item, types, item_name: Incomplete | None=None): When the type of the item is not one of the valid options. """ -def _check_value(item, allowed_values, item_name: Incomplete | None=None, extra: Incomplete | None=None): +def _check_value( + item, + allowed_values, + item_name: Incomplete | None = None, + extra: Incomplete | None = None, +): """ Check the value of a parameter against a list of valid options. @@ -113,4 +115,4 @@ def _check_verbose(verbose: Any) -> int: ------- verbose : int The verbosity level as an integer. - """ \ No newline at end of file + """ diff --git a/pycrostates/utils/_config.pyi b/pycrostates/utils/_config.pyi index 464c9f1d..5e614283 100644 --- a/pycrostates/utils/_config.pyi +++ b/pycrostates/utils/_config.pyi @@ -11,6 +11,7 @@ def _get_config_path(): def _get_data_path(): """Get pycrostates data directory.""" + default_config: Incomplete def _save_config(config) -> None: @@ -34,4 +35,4 @@ def set_config(key, value) -> None: The preference key to set. Must be a valid key. value : str | None The value to assign to the preference key. - """ \ No newline at end of file + """ diff --git a/pycrostates/utils/_docs.pyi b/pycrostates/utils/_docs.pyi index 7deb289f..0cf2b0fa 100644 --- a/pycrostates/utils/_docs.pyi +++ b/pycrostates/utils/_docs.pyi @@ -67,4 +67,4 @@ def copy_doc(source: Callable) -> Callable: ... pass >>> print(B.m1.__doc__) Docstring for m1 this gets appended - """ \ No newline at end of file + """ diff --git a/pycrostates/utils/_fixes.pyi b/pycrostates/utils/_fixes.pyi index 7b1dd3e8..08a8ce6f 100644 --- a/pycrostates/utils/_fixes.pyi +++ b/pycrostates/utils/_fixes.pyi @@ -5,5 +5,4 @@ class _WrapStdOut: properly. """ - def __getattr__(self, name): - ... \ No newline at end of file + def __getattr__(self, name): ... diff --git a/pycrostates/utils/_imports.pyi b/pycrostates/utils/_imports.pyi index 7de6b417..96572acc 100644 --- a/pycrostates/utils/_imports.pyi +++ b/pycrostates/utils/_imports.pyi @@ -2,7 +2,7 @@ from ._logs import logger as logger _INSTALL_MAPPING: dict[str, str] -def import_optional_dependency(name: str, extra: str='', raise_error: bool=True): +def import_optional_dependency(name: str, extra: str = "", raise_error: bool = True): """ Import an optional dependency. @@ -27,4 +27,4 @@ def import_optional_dependency(name: str, extra: str='', raise_error: bool=True) maybe_module : Optional[ModuleType] The imported module when found. None is returned when the package is not found and raise_error is False. - """ \ No newline at end of file + """ diff --git a/pycrostates/utils/_logs.pyi b/pycrostates/utils/_logs.pyi index 687db359..05bff187 100644 --- a/pycrostates/utils/_logs.pyi +++ b/pycrostates/utils/_logs.pyi @@ -9,7 +9,7 @@ from ._checks import _check_verbose as _check_verbose from ._docs import fill_doc as fill_doc from ._fixes import _WrapStdOut as _WrapStdOut -def _init_logger(*, verbose: Optional[Union[bool, str, int]]=None) -> logging.Logger: +def _init_logger(*, verbose: Optional[Union[bool, str, int]] = None) -> logging.Logger: """Initialize a logger. Assigns sys.stdout as the first handler of the logger. @@ -28,7 +28,13 @@ def _init_logger(*, verbose: Optional[Union[bool, str, int]]=None) -> logging.Lo The initialized logger. """ -def add_file_handler(fname: Union[str, Path], mode: str='a', encoding: Optional[str]=None, *, verbose: Optional[Union[bool, str, int]]=None) -> None: +def add_file_handler( + fname: Union[str, Path], + mode: str = "a", + encoding: Optional[str] = None, + *, + verbose: Optional[Union[bool, str, int]] = None, +) -> None: """Add a file handler to the logger. Parameters @@ -46,7 +52,9 @@ def add_file_handler(fname: Union[str, Path], mode: str='a', encoding: Optional[ ``"WARNING"`` for False and to ``"INFO"`` for True. """ -def set_log_level(verbose: Union[bool, str, int, None], apply_to_mne: bool=True) -> None: +def set_log_level( + verbose: Union[bool, str, int, None], apply_to_mne: bool = True +) -> None: """Set the log level for the logger and the first handler ``sys.stdout``. Parameters @@ -62,11 +70,10 @@ def set_log_level(verbose: Union[bool, str, int, None], apply_to_mne: bool=True) class _LoggerFormatter(logging.Formatter): """Format string Syntax for pycrostates.""" - _formatters: Incomplete - def __init__(self) -> None: - ... + _formatters: Incomplete + def __init__(self) -> None: ... def format(self, record): """ Format the received log record. @@ -101,15 +108,12 @@ class _use_log_level: verbosity is set to ``"WARNING"``. If a bool is provided, the verbosity is set to ``"WARNING"`` for False and to ``"INFO"`` for True. """ + _old_level: Incomplete _level: Incomplete - def __init__(self, verbose: Optional[Union[bool, str, int]]=None) -> None: - ... - - def __enter__(self) -> None: - ... + def __init__(self, verbose: Optional[Union[bool, str, int]] = None) -> None: ... + def __enter__(self) -> None: ... + def __exit__(self, *args) -> None: ... - def __exit__(self, *args) -> None: - ... -logger: Incomplete \ No newline at end of file +logger: Incomplete diff --git a/pycrostates/utils/mixin.pyi b/pycrostates/utils/mixin.pyi index c2f8716d..b0198197 100644 --- a/pycrostates/utils/mixin.pyi +++ b/pycrostates/utils/mixin.pyi @@ -7,14 +7,10 @@ class ChannelsMixin: """Channels Mixin for futur implementation.""" class ContainsMixin(MNEContainsMixin): - - def __contains__(self, ch_type) -> bool: - ... - + def __contains__(self, ch_type) -> bool: ... def __getattribute__(self, name): """Attribute getter.""" class MontageMixin(MNEMontageMixin): - def __getattribute__(self, name): - """Attribute getter.""" \ No newline at end of file + """Attribute getter.""" diff --git a/pycrostates/utils/sys_info.pyi b/pycrostates/utils/sys_info.pyi index bd53f3af..7b9374a5 100644 --- a/pycrostates/utils/sys_info.pyi +++ b/pycrostates/utils/sys_info.pyi @@ -4,7 +4,7 @@ from packaging.requirements import Requirement from ._checks import _check_type as _check_type -def sys_info(fid: Optional[IO]=None, developer: bool=False): +def sys_info(fid: Optional[IO] = None, developer: bool = False): """Print the system information for debugging. Parameters @@ -16,5 +16,7 @@ def sys_info(fid: Optional[IO]=None, developer: bool=False): If True, display information about optional dependencies. """ -def _list_dependencies_info(out: Callable, ljust: int, package: str, dependencies: list[Requirement]): - """List dependencies names and versions.""" \ No newline at end of file +def _list_dependencies_info( + out: Callable, ljust: int, package: str, dependencies: list[Requirement] +): + """List dependencies names and versions.""" diff --git a/pycrostates/utils/utils.pyi b/pycrostates/utils/utils.pyi index 3c34489a..24ca6443 100644 --- a/pycrostates/utils/utils.pyi +++ b/pycrostates/utils/utils.pyi @@ -2,7 +2,7 @@ from _typeshed import Incomplete from ._logs import logger as logger -def _corr_vectors(A, B, axis: int=0): +def _corr_vectors(A, B, axis: int = 0): """Compute pairwise correlation of multiple pairs of vectors. Fast way to compute correlation of multiple pairs of vectors without computing all @@ -26,8 +26,8 @@ def _corr_vectors(A, B, axis: int=0): For each pair of vectors, the correlation between them. """ -def _distance_matrix(X, Y: Incomplete | None=None): +def _distance_matrix(X, Y: Incomplete | None = None): """Distance matrix used in metrics.""" def _compare_infos(cluster_info, inst_info): - """Check that channels in cluster_info are all present in inst_info.""" \ No newline at end of file + """Check that channels in cluster_info are all present in inst_info.""" diff --git a/pycrostates/viz/cluster_centers.pyi b/pycrostates/viz/cluster_centers.pyi index 96324034..40d64698 100644 --- a/pycrostates/viz/cluster_centers.pyi +++ b/pycrostates/viz/cluster_centers.pyi @@ -12,7 +12,18 @@ from ..utils._logs import logger as logger _GRADIENT_KWARGS_DEFAULTS: dict[str, str] -def plot_cluster_centers(cluster_centers: NDArray[float], info: Union[Info, CHInfo], cluster_names: list[str]=None, axes: Optional[Union[Axes, NDArray[Axes]]]=None, show_gradient: Optional[bool]=False, gradient_kwargs: dict[str, Any]=..., *, block: bool=False, verbose: Optional[str]=None, **kwargs): +def plot_cluster_centers( + cluster_centers: NDArray[float], + info: Union[Info, CHInfo], + cluster_names: list[str] = None, + axes: Optional[Union[Axes, NDArray[Axes]]] = None, + show_gradient: Optional[bool] = False, + gradient_kwargs: dict[str, Any] = ..., + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, +): """Create topographic maps for cluster centers. Parameters @@ -46,4 +57,4 @@ def plot_cluster_centers(cluster_centers: NDArray[float], info: Union[Info, CHIn ------- fig : Figure Matplotlib figure(s) on which topographic maps are plotted. - """ \ No newline at end of file + """ diff --git a/pycrostates/viz/segmentation.pyi b/pycrostates/viz/segmentation.pyi index 466ada87..7790751d 100644 --- a/pycrostates/viz/segmentation.pyi +++ b/pycrostates/viz/segmentation.pyi @@ -10,7 +10,21 @@ from ..utils._checks import _check_type as _check_type from ..utils._docs import fill_doc as fill_doc from ..utils._logs import logger as logger -def plot_raw_segmentation(labels: NDArray[int], raw: BaseRaw, n_clusters: int, cluster_names: list[str]=None, tmin: Optional[Union[int, float]]=None, tmax: Optional[Union[int, float]]=None, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Optional[str]=None, **kwargs): +def plot_raw_segmentation( + labels: NDArray[int], + raw: BaseRaw, + n_clusters: int, + cluster_names: list[str] = None, + tmin: Optional[Union[int, float]] = None, + tmax: Optional[Union[int, float]] = None, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, +): """Plot raw segmentation. Parameters @@ -51,7 +65,19 @@ def plot_raw_segmentation(labels: NDArray[int], raw: BaseRaw, n_clusters: int, c Matplotlib figure(s) on which topographic maps are plotted. """ -def plot_epoch_segmentation(labels: NDArray[int], epochs: BaseEpochs, n_clusters: int, cluster_names: list[str]=None, cmap: Optional[str]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, block: bool=False, verbose: Optional[str]=None, **kwargs): +def plot_epoch_segmentation( + labels: NDArray[int], + epochs: BaseEpochs, + n_clusters: int, + cluster_names: list[str] = None, + cmap: Optional[str] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + block: bool = False, + verbose: Optional[str] = None, + **kwargs, +): """ Plot epochs segmentation. @@ -89,7 +115,19 @@ def plot_epoch_segmentation(labels: NDArray[int], epochs: BaseEpochs, n_clusters Matplotlib figure on which topographic maps are plotted. """ -def _plot_segmentation(labels: NDArray[int], gfp: NDArray[float], times: NDArray[float], n_clusters: int, cluster_names: list[str]=None, cmap: Optional[Union[str, colors.Colormap]]=None, axes: Optional[Axes]=None, cbar_axes: Optional[Axes]=None, *, verbose: Optional[str]=None, **kwargs): +def _plot_segmentation( + labels: NDArray[int], + gfp: NDArray[float], + times: NDArray[float], + n_clusters: int, + cluster_names: list[str] = None, + cmap: Optional[Union[str, colors.Colormap]] = None, + axes: Optional[Axes] = None, + cbar_axes: Optional[Axes] = None, + *, + verbose: Optional[str] = None, + **kwargs, +): """Code snippet to plot segmentation for raw and epochs.""" def _compatibility_cmap(cmap: Optional[Union[str, colors.Colormap]], n_colors: int): @@ -97,4 +135,4 @@ def _compatibility_cmap(cmap: Optional[Union[str, colors.Colormap]], n_colors: i Matplotlib 3.6 introduced a deprecation of plt.cm.get_cmap(). When support for the 3.6 version is dropped, this checker can be removed. - """ \ No newline at end of file + """ diff --git a/pyproject.toml b/pyproject.toml index aa4a3f4b..ef13c457 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,13 +156,10 @@ minversion = '6.0' [tool.ruff] extend-exclude = [ - '*.pyi', 'doc', 'setup.py', ] -ignore = [ - "UP007", # 'Use `X | Y` for type annotations', requires python 3.10 -] +ignore = [] line-length = 88 select = ["E", "F", "UP", "W"] target-version = 'py39' @@ -171,6 +168,10 @@ target-version = 'py39' docstring-code-format = true [tool.ruff.per-file-ignores] +'*' = [ + "UP007", # 'Use `X | Y` for type annotations', requires python 3.10 +] +'*.pyi' = ['E501'] '__init__.py' = ['F401'] [tool.setuptools] From bb824918d3ab9c0151df2d63b1985808fed343c0 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Thu, 18 Jan 2024 11:41:40 +0100 Subject: [PATCH 20/26] Improve configuration of sphinx-copybutton (#149) * improve configuration of sphinx-copybutton * better --- docs/source/conf.py | 11 ++++++----- docs/source/install.rst | 4 ++-- pycrostates/utils/_docs.py | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 6a195c40..770acfcb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -275,8 +275,9 @@ def append_attr_meth_examples(app, what, name, obj, options, lines): .. _sphx_glr_backreferences_{1}: .. rubric:: Examples using ``{0}``: .. minigallery:: {1} -""".format( - name.split(".")[-1], name - ).split( - "\n" - ) +""".format(name.split(".")[-1], name).split("\n") + + +# -- sphinx_copybutton ----------------------------------------------------------------- +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True diff --git a/docs/source/install.rst b/docs/source/install.rst index 84de6bff..cbca5f7e 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -40,7 +40,7 @@ Methods .. code-block:: bash - pip install pycrostates + $ pip install pycrostates .. tab-item:: Conda @@ -48,7 +48,7 @@ Methods .. code-block:: bash - conda install -c conda-forge pycrostates + $ conda install -c conda-forge pycrostates .. tab-item:: Snapshot of the current version diff --git a/pycrostates/utils/_docs.py b/pycrostates/utils/_docs.py index 5a5c138d..486a9c63 100644 --- a/pycrostates/utils/_docs.py +++ b/pycrostates/utils/_docs.py @@ -4,6 +4,7 @@ Inspired from mne: https://mne.tools/stable/index.html Inspired from mne.utils.docs.py by Eric Larson """ + import sys from typing import Callable From 589f695aba8754f86a6036d70c4fb10f4e123c0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:52:42 +0100 Subject: [PATCH 21/26] Bump actions/cache from 3 to 4 (#150) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pytest.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index c4db7be5..fa0c35fb 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -44,7 +44,7 @@ jobs: curl https://raw.githubusercontent.com/mne-tools/mne-testing-data/master/version.txt -o mne_testing_data_version.txt - name: Cache testing dataset id: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: mne-testing-${{ runner.os }}-${{ hashFiles('mne_testing_data_version.txt') }} path: ~/mne_data @@ -99,7 +99,7 @@ jobs: curl https://raw.githubusercontent.com/mne-tools/mne-testing-data/master/version.txt -o mne_testing_data_version.txt - name: Cache testing dataset id: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: mne-testing-${{ runner.os }}-${{ hashFiles('mne_testing_data_version.txt') }} path: ~/mne_data From c4d50b780c324d5c6f6b1824fe1c530dfb7d00f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:15:50 +0100 Subject: [PATCH 22/26] [pre-commit.ci] pre-commit autoupdate (#151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.13 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.13...v0.1.14) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95d3258f..223782bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: files: pycrostates - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.1.14 hooks: - id: ruff name: ruff linter From 643d7b723364aa493014541692ca5c146963ab03 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:06:49 +0000 Subject: [PATCH 23/26] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pycrostates/segmentation/entropy.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pycrostates/segmentation/entropy.py b/pycrostates/segmentation/entropy.py index b683b5d6..785e4182 100644 --- a/pycrostates/segmentation/entropy.py +++ b/pycrostates/segmentation/entropy.py @@ -6,7 +6,7 @@ """ import itertools -from typing import List, Optional, Tuple, Union +from typing import Optional, Union import numpy as np import scipy.stats @@ -232,7 +232,7 @@ def _excess_entropy_rate( state_to_ignore: Optional[int] = -1, log_base: Union[float, str] = 2, n_jobs: int = 1, -) -> Tuple[float, float, float, NDArray[int], NDArray[float]]: +) -> tuple[float, float, float, NDArray[int], NDArray[float]]: """Estimate the entropy rate and the excess_entropy from a linear fit. Parameters @@ -278,7 +278,7 @@ def excess_entropy_rate( ignore_repetitions: bool = False, log_base: Union[float, str] = 2, n_jobs: int = 1, -) -> Tuple[float, float, float, NDArray[int], NDArray[float]]: +) -> tuple[float, float, float, NDArray[int], NDArray[float]]: r"""Estimate the entropy rate and the ``excess_entropy`` of the segmentation. The entropy rate and the ``excess_entropy`` are estimated from a linear fit: @@ -382,14 +382,14 @@ def auto_information_function( segmentation: Segmentation, lags: Union[ int, - List[int], - Tuple[int, ...], + list[int], + tuple[int, ...], NDArray[int], ], ignore_repetitions: bool = False, log_base: Union[float, str] = 2, n_jobs: int = 1, -) -> Tuple[NDArray[int], NDArray[float]]: +) -> tuple[NDArray[int], NDArray[float]]: r"""Compute the Auto-information function (aif). Compute the Auto-information function (aif) as described @@ -493,14 +493,14 @@ def partial_auto_information_function( segmentation: Segmentation, lags: Union[ int, - List[int], - Tuple[int, ...], + list[int], + tuple[int, ...], NDArray[int], ], ignore_repetitions: bool = False, log_base: Union[float, str] = 2, n_jobs: Optional[int] = 1, -) -> Tuple[NDArray[int], NDArray[float]]: +) -> tuple[NDArray[int], NDArray[float]]: r"""Compute the Partial auto-information function. Compute the Partial auto-information function as described From 5965b6b0e02d90863b714e1c3e445c1cee80f36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20F=C3=A9rat?= Date: Wed, 31 Jan 2024 12:32:47 +0100 Subject: [PATCH 24/26] Add state_to_ignore in docdict --- .gitignore | 1 + pycrostates/utils/_docs.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 509e9877..8ed17982 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,4 @@ dmypy.json # ---------------------------------------------------------------------------- .DS_Store junit-results.xml +docs/source/sg_execution_times.rst diff --git a/pycrostates/utils/_docs.py b/pycrostates/utils/_docs.py index 486a9c63..4cdb3cc4 100644 --- a/pycrostates/utils/_docs.py +++ b/pycrostates/utils/_docs.py @@ -145,6 +145,10 @@ * ``percent``: normalize count such as the probabilities along the first axis is always equal to ``100``.""" +docdict["state_to_ignore"] = """ +state_to_ignore : int | None + Ignore state with symbol ``state_to_ignore`` from analysis.""" + # -- T --------------------------------------------------------------------------------- docdict["transition_matrix"] = """ T : array of shape ``(n_cluster, n_cluster)`` From 722b20d0202ba01b72d38ab88ccfaefe1c425cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20F=C3=A9rat?= Date: Wed, 31 Jan 2024 12:37:51 +0100 Subject: [PATCH 25/26] Add log_base and labels_info to docdict --- pycrostates/utils/_docs.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pycrostates/utils/_docs.py b/pycrostates/utils/_docs.py index 4cdb3cc4..52fd9a35 100644 --- a/pycrostates/utils/_docs.py +++ b/pycrostates/utils/_docs.py @@ -102,6 +102,10 @@ labels : array of shape ``(n_epochs, n_samples)`` Microstates labels attributed to each sample, i.e. the segmentation.""" +docdict["labels_info"] = """ +labels : array (n_symbols, ) + Microstate symbolic sequence.""" + docdict["labels_raw"] = """ labels : array of shape ``(n_samples,)`` Microstates labels attributed to each sample, i.e. the segmentation.""" @@ -110,6 +114,15 @@ labels : array of shape ``(n_samples,)`` or ``(n_epochs, n_samples)`` Microstates labels attributed to each sample, i.e. the segmentation.""" +docdict["log_base"] = """ +log_base : float | str + The log base to use. + If string: + * ``bits``: log_base = ``2`` + * ``natural``: log_base = ``np.e`` + * ``dits``: log_base = ``10`` + Default to ``bits``.""" + # -- M --------------------------------------------------------------------------------- # -- N --------------------------------------------------------------------------------- docdict["n_clusters"] = """ From 2b34e1c5147a4047e56e10d2b1c83af1985c98e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20F=C3=A9rat?= Date: Wed, 31 Jan 2024 12:47:05 +0100 Subject: [PATCH 26/26] Add segmentation to docdict --- pycrostates/utils/_docs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pycrostates/utils/_docs.py b/pycrostates/utils/_docs.py index 52fd9a35..4e4f2d51 100644 --- a/pycrostates/utils/_docs.py +++ b/pycrostates/utils/_docs.py @@ -139,6 +139,10 @@ # -- Q --------------------------------------------------------------------------------- # -- R --------------------------------------------------------------------------------- # -- S --------------------------------------------------------------------------------- +docdict["segmentation"] = """ +segmentation : RawSegmentation | EpochsSegmentation + Segmentation object containing the microstate symbolic sequence.""" + docdict["stat_expected_transitions"] = """ stat : str Aggregate statistic to compute transitions. Can be: