From af934b43618ad446c251811cc08b5221d26e7aff Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 7 Apr 2022 13:12:16 +0200 Subject: [PATCH 01/22] Add common tools for MATLAB and Python testing Common base for the matlab and python testing branch to minimize merge conflicts down the line --- .gitignore | 11 ++-- .pre-commit-config.yaml | 7 +++ CONTRIBUTING.md | 56 +++++++++++++++++++ Makefile | 116 ++++++++++++++++++++++++++++++++++++++++ README.md | 35 ++++++------ requirements_dev.txt | 11 ++++ 6 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md create mode 100644 Makefile create mode 100644 requirements_dev.txt diff --git a/.gitignore b/.gitignore index 7c82424..7bd97a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -# +# vscode files .vscode/ +# test related +tests/outputs/ -# data folder +# data folders data/ # example output folders @@ -13,6 +15,9 @@ figures/ report.html ##*.png +# pyenv +Pipfile + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -54,6 +59,7 @@ pip-delete-this-directory.txt htmlcov/ .tox/ .coverage +coverage_html .coverage.* .cache nosetests.xml @@ -153,4 +159,3 @@ codegen/ # Octave session info octave-workspace - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e9f04ad --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-yaml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..baaa794 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# CONTRIBUTING + +Information for anyone who would like to contribute to this repository. + +## Repository map + +```bash +├── .git +├── .github +│ └── workflows # Github continuous integration set up +├── examples +│ ├── data +│ ├── example1outputs +│ ├── example2outputs +├── glmsingle # Python implementation +│ ├── cod +│ ├── design +│ ├── gmm +│ ├── hrf +│ ├── ols +│ ├── ssq +│ └── utils +├── matlab # Matlab implementation +│ ├── examples +│ ├── fracridge +│ └── utilities +└── tests # Python and Matlab tests + └── data + +``` + +## Generic + +### Makefile + +### pre-commit + +## Matlab + +### Style guide + +### Tests + +#### Demos + +### Continuous integration + +## Python + +### Style guide + +### Tests + +#### Demos + +### Continuous integration \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..75e39ce --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +.DEFAULT_GOAL := help + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +# TODO make more general to use the local matlab version +MATLAB = /usr/local/MATLAB/R2017a/bin/matlab +MATLAB_ARG = -nodisplay -nosplash -nodesktop + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +# determines what "make help" will show +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +################################################################################ +# GENERIC +.PHONY: help clean clean-test lint install_dev + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage artifacts + +clean-test: ## remove test and coverage artifacts + rm -rf .tox/ + rm -rf .coverage + rm -rf htmlcov/ + rm -rf .pytest_cache + rm -rf tests/data + rm -rf test/outputs + +install_dev: ## install for both matlab and python developpers + pip install -e . + pip install -r requirements_dev.txt + +lint: lint/black lint/flake8 lint/miss_hit ## check style + +test: test-matlab test-python + +tests/data/nsdcoreexampledataset.mat: + mkdir tests/data + curl -fsSL --retry 5 -o "tests/data/nsdcoreexampledataset.mat" https://osf.io/k89b2/download + +################################################################################ + +################################################################################ +# MATLAB + +.PHONY: lint/miss_hit + +lint/miss_hit: ## lint and checks matlab code + mh_style --fix tests && mh_metric --ci tests && mh_lint tests + +test-matlab: tests/data/nsdcoreexampledataset.mat + $(MATLAB) $(MATLAB_ARG) -r "run_tests; exit()" + +coverage-matlab: test-matlab + $(BROWSER) coverage_html/index.html + +################################################################################ + +################################################################################ +# PYTHON + +.PHONY: clean-build clean-pyc coverage-python install lint/flake8 lint/black + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +lint/flake8: ## check style with flake8 + flake8 tests +lint/black: ## check style with black + black tests + +test-python: tests/data/nsdcoreexampledataset.mat ## run tests quickly with the default Python + pytest + +test-notebooks: + pytest --nbmake --nbmake-timeout=3000 "./examples" +test-all: ## run tests on every Python version with tox + tox + +coverage-python: ## check code coverage quickly with the default Python + coverage run --source glmsingle -m pytest + coverage report -m + coverage html + $(BROWSER) htmlcov/index.html + +install: clean ## install the package to the active Python's site-packages + python setup.py install + +################################################################################ \ No newline at end of file diff --git a/README.md b/README.md index 1cc319e..336ed01 100644 --- a/README.md +++ b/README.md @@ -33,20 +33,6 @@ This will also clone [`fracridge`](https://github.com/nrdg/fracridge) as a submo To use the GLMsingle toolbox, add it and `fracridge` to your MATLAB path by running the `setup.m` script. -## Example scripts - -We provide a number of example scripts that demonstrate usage of GLMsingle. You can browse these example scripts here: - -(Python Example 1 - event-related design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/examples/example1.html - -(Python Example 2 - block design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/examples/example2.html - -(MATLAB Example 1 - event-related design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/matlab/examples/example1preview/example1.html - -(MATLAB Example 2 - block design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/matlab/examples/example2preview/example2.html - -If you would like to run these example scripts, the Python versions are available in `/GLMsingle/examples`, and the MATLAB versions are available in `/GLMsingle/matlab/examples`. Each notebook contains a full walkthrough of the process of loading an example dataset and design matrix, estimating neural responses using GLMsingle, estimating the reliability of responses at each voxel, and comparing those achieved via GLMsingle to those achieved using a baseline GLM. - ## Python To install: @@ -63,12 +49,25 @@ Running the demos requires: pip install jupyterlab ``` -Code dependencies: see requirements.txt +Code dependencies: see [requirements.txt](./requirements.txt) Notes: -* Please note that GLMsingle is not (yet) compatible with Python 3.9 (due to an incompatibility between scikit-learn and Python 3.9). Please use Python 3.8 or earlier. * Currently, numpy has a 4GB limit for the pickle files it writes; thus, GLMsingle will crash if the file outputs exceed that size. One workaround is to turn off "disk saving" and instead get the outputs of GLMsingle in your workspace and save the outputs yourself to HDF5 format. +## Example scripts + +We provide a number of example scripts that demonstrate usage of GLMsingle. You can browse these example scripts here: + +(Python Example 1 - event-related design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/examples/example1.html + +(Python Example 2 - block design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/examples/example2.html + +(MATLAB Example 1 - event-related design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/matlab/examples/example1preview/example1.html + +(MATLAB Example 2 - block design) https://htmlpreview.github.io/?https://github.com/kendrickkay/GLMsingle/blob/main/matlab/examples/example2preview/example2.html + +If you would like to run these example scripts, the Python versions are available in `/GLMsingle/examples`, and the MATLAB versions are available in `/GLMsingle/matlab/examples`. Each notebook contains a full walkthrough of the process of loading an example dataset and design matrix, estimating neural responses using GLMsingle, estimating the reliability of responses at each voxel, and comparing those achieved via GLMsingle to those achieved using a baseline GLM. + ## Additional information For additional information, please visit the Wiki page associated with this @@ -80,6 +79,10 @@ If you use GLMsingle in your research, please cite the following paper: * [Prince, J.S., Charest, I., Kurzawski, J.W., Pyles, J.A., Tarr, M.J., Kay, K.N. GLMsingle: a toolbox for improving single-trial fMRI response estimates. bioRxiv (2022).](https://www.biorxiv.org/content/10.1101/2022.01.31.478431v1) +## Contributing + +If you want to contribute to GLMsingle see the [contributing](./CONTRIBUTING.md) documentation to help you know what is where and how to set things up. + ## Change history * 2021/10/12 - Version 1.0 of GLMsingle is now released. A git tag has been added to the repo. diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..8bf205e --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,11 @@ +# Matlab dev +miss_hit + +# Python dev +flake8 +tox +coverage +pytest +black +nbmake +pytest-cov From 5dfcecdcd4016846c2858e0483cb034abd25a1d7 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 7 Apr 2022 13:31:02 +0200 Subject: [PATCH 02/22] pin minimum python and dependencies versions --- requirements.txt | 16 ++++++++-------- setup.py | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index b0de2fa..6daf04e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -numpy -scipy -sklearn -matplotlib -tqdm -fracridge -nibabel -h5py +fracridge>=1.4.3 +h5py>=3.1.0 +matplotlib>=3.3.4 +nibabel>=3.2.2 +numpy>=1.17.0 +scikit-learn>=0.23.2 +scipy>=1.5.4 +tqdm>=4.63.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 731c9c2..3847fc8 100644 --- a/setup.py +++ b/setup.py @@ -28,5 +28,6 @@ packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=requires + install_requires=requires, + python_requires='>=3.6' ) From ddbf5b242b0b6872f70b031f48461d5ef143ca1e Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 7 Apr 2022 13:31:27 +0200 Subject: [PATCH 03/22] add config for flake8 and pytest --- setup.cfg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..346b95e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,21 @@ +[flake8] +max-line-length = 99 + +ignore = D203, W503 + +exclude = + .git, + docs, + __pycache__, + old, + build, + dist + 'matlab/fracridge' + +max-complexity = 10 + +[tool:pytest] +branch = True +source = glmsingle/* +include = glmsingle/* +testpaths = tests \ No newline at end of file From 8a7c1f59ac054d1e373051182f34783ea698362c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 7 Apr 2022 13:31:42 +0200 Subject: [PATCH 04/22] add smoke test --- tests/__init__.py | 1 + tests/test_glmsingleunit.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/test_glmsingleunit.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..bb6783a --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Unit test package for glmsingle.""" diff --git a/tests/test_glmsingleunit.py b/tests/test_glmsingleunit.py new file mode 100644 index 0000000..383af18 --- /dev/null +++ b/tests/test_glmsingleunit.py @@ -0,0 +1,34 @@ +from os.path import join +from os.path import abspath +from os.path import dirname +import scipy +import scipy.io as sio +from glmsingle.glmsingle import GLM_single + +test_dir = dirname(abspath(__file__)) +data_dir = join(test_dir, "data") +data_file = join(data_dir, "nsdcoreexampledataset.mat") + +output_dir = join(test_dir, "outputs") + + +def test_smoke_test(): + + X = sio.loadmat(data_file) + + data = [] + design = [] + for r in range(3): # range(len(X['data'][0])): + data.append(X["data"][0, r][50:70, 7:27, 0:1, :]) + design.append(scipy.sparse.csr_matrix.toarray(X["design"][0, r])) + + stimdur = X["stimdur"][0][0] + tr = X["tr"][0][0] + + opt = {"wantmemoryoutputs": [1, 1, 1, 1]} + + # OPTIONAL: PUT THIS IN? + # opt['wantlibrary'] = 0 + + glmsingle_obj = GLM_single(opt) + glmsingle_obj.fit(design, data, stimdur, tr, outputdir=join(output_dir, "python")) From 53c1804d18dbdd7192a06fde91a7fe137c5f346e Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 7 Apr 2022 13:42:08 +0200 Subject: [PATCH 05/22] automate python test with Github CI --- .github/workflows/run_tests_python.yml | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/run_tests_python.yml diff --git a/.github/workflows/run_tests_python.yml b/.github/workflows/run_tests_python.yml new file mode 100644 index 0000000..8feb8ab --- /dev/null +++ b/.github/workflows/run_tests_python.yml @@ -0,0 +1,54 @@ +name: tests and coverage with python + +on: + push: + branches: ["*"] + pull_request: + branches: ["*"] + +jobs: + tests: + continue-on-error: true + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04] + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - name: Shallow clone GLMsingle + uses: actions/checkout@v3 + with: + submodules: "recursive" + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + pip install . + pip install -r requirements_dev.txt + + - name: Display Python version and packages + run: | + python -c "import sys; print(sys.version)" + pip list + + - name: Prepare data + run: make tests/data/nsdcoreexampledataset.mat + + - name: Run tests and generate coverage report + run: pytest -v --cov glmsingle --cov-report xml tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + file: ./coverage.xml + flags: python + name: codecov-umbrella + fail_ci_if_error: true + verbose: true From 922c8d0c0d17f40e23beb405cf9537ab736c911c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 7 Apr 2022 13:42:31 +0200 Subject: [PATCH 06/22] automate testing python demos with github CI --- .github/workflows/run_demos_python.yml | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/run_demos_python.yml diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml new file mode 100644 index 0000000..bd3a3f0 --- /dev/null +++ b/.github/workflows/run_demos_python.yml @@ -0,0 +1,42 @@ +name: run python demos + +on: + push: + branches: ["*"] + pull_request: + branches: ["*"] + +jobs: + demos: + continue-on-error: true + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04] + python-version: ["3.7"] + + steps: + - name: Shallow clone GLMsingle + uses: actions/checkout@v3 + with: + submodules: "recursive" + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + pip install . + pip install -r requirements_dev.txt + + - name: Display Python version and packages + run: | + python -c "import sys; print(sys.version)" + pip list + + - name: Run tests and generate coverage report + run: pytest --nbmake --nbmake-timeout=3000 "./examples" From 3ee979b297364dd0960b76e60112f8c2bae09c69 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 10:51:17 +0200 Subject: [PATCH 07/22] update python versions to use in CI --- .github/workflows/run_demos_python.yml | 2 +- .github/workflows/run_tests_python.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml index bd3a3f0..aaa5056 100644 --- a/.github/workflows/run_demos_python.yml +++ b/.github/workflows/run_demos_python.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - python-version: ["3.7"] + python-version: ["3.9"] steps: - name: Shallow clone GLMsingle diff --git a/.github/workflows/run_tests_python.yml b/.github/workflows/run_tests_python.yml index 8feb8ab..623c1d4 100644 --- a/.github/workflows/run_tests_python.yml +++ b/.github/workflows/run_tests_python.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9"] # fails on 3.10 steps: - name: Shallow clone GLMsingle From d251cee65899ada77dab2879fdbe03220a9258ca Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 10:51:38 +0200 Subject: [PATCH 08/22] add base requirements for dev install --- requirements_dev.txt | 3 +++ setup.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 8bf205e..f3f37b0 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,3 +1,6 @@ +# Base requirements +-r requirements.txt + # Matlab dev miss_hit diff --git a/setup.py b/setup.py index 3847fc8..ce66528 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='GLMsingle', - version='0.0.1', + version='1.0.0', description='Python GLMsingle', url='https://github.com/kendrickkay/GLMsingle', long_description=long_description, @@ -29,5 +29,5 @@ include_package_data=True, zip_safe=False, install_requires=requires, - python_requires='>=3.6' + python_requires='>=3.6, <3.10' ) From 588690658a002bd06fc2631aeeda815bb0227441 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 11:00:15 +0200 Subject: [PATCH 09/22] add CRON job for demos in CI and fail tests ealy --- .github/workflows/run_demos_python.yml | 24 +++++++++++++++++++++--- .github/workflows/run_tests_python.yml | 4 ++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml index aaa5056..c86e5c1 100644 --- a/.github/workflows/run_demos_python.yml +++ b/.github/workflows/run_demos_python.yml @@ -1,10 +1,28 @@ -name: run python demos +name: Python demos + +# Uses the cron schedule for github actions +# +# will run at 00H00 run on the 1 and 15 of every month +# +# https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#scheduled-events +# +# ┌───────────── minute (0 - 59) +# │ ┌───────────── hour (0 - 23) +# │ │ ┌───────────── day of the month (1 - 31) +# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC): * means all +# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT): * means all +# │ │ │ │ │ +# │ │ │ │ │ +# │ │ │ │ │ +# +# - cron "0 0 1,15 * *" on: + schedule: + - cron: "0 0 1,15 * *" + push: branches: ["*"] - pull_request: - branches: ["*"] jobs: demos: diff --git a/.github/workflows/run_tests_python.yml b/.github/workflows/run_tests_python.yml index 623c1d4..399a715 100644 --- a/.github/workflows/run_tests_python.yml +++ b/.github/workflows/run_tests_python.yml @@ -1,4 +1,4 @@ -name: tests and coverage with python +name: Python test on: push: @@ -8,12 +8,12 @@ on: jobs: tests: - continue-on-error: true runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-20.04] python-version: ["3.7", "3.8", "3.9"] # fails on 3.10 + fail-fast: true # cancel all jobs if one fails steps: - name: Shallow clone GLMsingle From 78e072797f3e1f9766c7ac80093035a5bdf96c20 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 11:36:07 +0200 Subject: [PATCH 10/22] only use pytest-cov for coverage --- .github/workflows/run_demos_python.yml | 2 +- Makefile | 8 +++----- requirements_dev.txt | 7 +++---- setup.cfg | 3 --- tests/{test_glmsingleunit.py => test_GLM_single.py} | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) rename tests/{test_glmsingleunit.py => test_GLM_single.py} (96%) diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml index c86e5c1..a291f46 100644 --- a/.github/workflows/run_demos_python.yml +++ b/.github/workflows/run_demos_python.yml @@ -57,4 +57,4 @@ jobs: pip list - name: Run tests and generate coverage report - run: pytest --nbmake --nbmake-timeout=3000 "./examples" + run: pytest --nbmake --nbmake-timeout=3000 "./examples" diff --git a/Makefile b/Makefile index 75e39ce..5f9169f 100644 --- a/Makefile +++ b/Makefile @@ -97,17 +97,15 @@ lint/black: ## check style with black black tests test-python: tests/data/nsdcoreexampledataset.mat ## run tests quickly with the default Python - pytest + pytest -vv test-notebooks: - pytest --nbmake --nbmake-timeout=3000 "./examples" + pytest -vv --nbmake --nbmake-timeout=3000 "./examples" test-all: ## run tests on every Python version with tox tox coverage-python: ## check code coverage quickly with the default Python - coverage run --source glmsingle -m pytest - coverage report -m - coverage html + pytest -vv --cov glmsingle --cov-report html:htmlcov $(BROWSER) htmlcov/index.html install: clean ## install the package to the active Python's site-packages diff --git a/requirements_dev.txt b/requirements_dev.txt index f3f37b0..af2058a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,10 +5,9 @@ miss_hit # Python dev +black flake8 -tox -coverage pytest -black -nbmake pytest-cov +nbmake +tox diff --git a/setup.cfg b/setup.cfg index 346b95e..99ef9c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,4 @@ exclude = max-complexity = 10 [tool:pytest] -branch = True -source = glmsingle/* -include = glmsingle/* testpaths = tests \ No newline at end of file diff --git a/tests/test_glmsingleunit.py b/tests/test_GLM_single.py similarity index 96% rename from tests/test_glmsingleunit.py rename to tests/test_GLM_single.py index 383af18..1474257 100644 --- a/tests/test_glmsingleunit.py +++ b/tests/test_GLM_single.py @@ -12,7 +12,7 @@ output_dir = join(test_dir, "outputs") -def test_smoke_test(): +def test_GLM_single_system(): X = sio.loadmat(data_file) From d5ee9bf9ad2de804bbff4508308c923be41818a0 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 16:28:38 +0200 Subject: [PATCH 11/22] test GLM_single against expected data --- tests/expected/python/README.md | 52 ++++++++++++++++++ .../expected/python/TYPEB_FITHRF_HRFindex.npy | Bin 0 -> 3328 bytes .../expected/python/TYPEC_FITHRF_HRFindex.npy | Bin 0 -> 3328 bytes .../expected/python/TYPED_FITHRF_HRFindex.npy | Bin 0 -> 3328 bytes tests/expected/python/TYPED_FITHRF_R2.npy | Bin 0 -> 1728 bytes tests/test_GLM_single.py | 39 ++++++++++++- 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 tests/expected/python/README.md create mode 100644 tests/expected/python/TYPEB_FITHRF_HRFindex.npy create mode 100644 tests/expected/python/TYPEC_FITHRF_HRFindex.npy create mode 100644 tests/expected/python/TYPED_FITHRF_HRFindex.npy create mode 100644 tests/expected/python/TYPED_FITHRF_R2.npy diff --git a/tests/expected/python/README.md b/tests/expected/python/README.md new file mode 100644 index 0000000..6bf1409 --- /dev/null +++ b/tests/expected/python/README.md @@ -0,0 +1,52 @@ +# Generate expected data + +Generated from the output of one run from the files in `tests/outputs/python` + +```python +import numpy as np + +tmp = np.load("TYPEB_FITHRF.npy", allow_pickle=True) +results = tmp.item(0)["HRFindex"] +np.save("TYPEB_FITHRF_HRFindex.npy", results) + +tmp = np.load("TYPEC_FITHRF_GLMDENOISE.npy", allow_pickle=True) +results = tmp.item(0)["HRFindex"] +np.save("TYPEC_FITHRF_HRFindex.npy", results) + +tmp = np.load("TYPED_FITHRF_GLMDENOISE_RR.npy", allow_pickle=True) +results = tmp.item(0)["HRFindex"] +np.save("TYPED_FITHRF_HRFindex.npy", results) + +results = tmp.item(0)["R2"] +np.save("TYPED_FITHRF_R2.npy", results) +``` + + diff --git a/tests/expected/python/TYPEB_FITHRF_HRFindex.npy b/tests/expected/python/TYPEB_FITHRF_HRFindex.npy new file mode 100644 index 0000000000000000000000000000000000000000..6c2f1a5692462cbf4cbcfe73295aac5ca006d5ae GIT binary patch literal 3328 zcmbu>yGnyW5C!0Pzu#Zr?|y|8Y9(TAr`T9nNiY!wQAxx`dn;oGqHeETx&4}R9qdrzamczyKH zdmW79{o})K^YeFf&}}}OzkF8rI=>&dS>m07+Xc4*<<)SbTV(){mjOjuB(Q)qdCcP7Rc%c4k$d`Sw zzVXuM55GM7P#w%V^P-!`hxsL%4_-07Y5l3E#}~9-^OxsGw}v?{G2hTSe&1Jo!D;;^ zPiyGnyW5C!0Pzu#Zr?|y|8Y9(TAr`T9nNiY!wQAxx`dn;oGqHeETx&4}R9qdrzamczyKH zdmW79{o})K^YeFf&}}}OzkF8rI=>&dS>m07+Xc4*<<)SbTV(){mjOjuB(Q)qdCcP7Rc%c4k$d`Sw zzVXuM55GM7P#w%V^P-!`hxsL%4_-07Y5l3E#}~9-^OxsGw}v?{G2hTSe&1Jo!D;;^ zPiy-LGD5Crh}t?^r5Al+XfMG6ZMYdgip!b*aPD2S0nY{aMVg^hT>X5dKBG{fHQ z&dl!JCHcC#xxBtzXuY>Sn!#{#KW@%C&H2-5)9o}5qw(9g|8h4P4~9SM7yakSaJoKu z?7t4D@zF`I*FElbK0AN@v=3XqAGlNE{epW1cLU|saJ%Hg%@QZJ%Q}6y5$ky9r9bQQ zB0jiP@t5l>C2y@D|EZgEqw`y`a>Eycnd67=~%JW|@eXK@5@?i3q zzy0M>kB?7%h#`OS-G4gb%y%#B%TGVxzF6OQ z>GOwQo_(keW}SJ_P2|J;63qv%nBKhp)YIb&TCe%b^P^kCoR^qyXdS=rE56{o{*tFQ zFY|DY`ba%{9V(!y32Ri8sPwwZu@IiVt z#A_~m$ft+Ty)&t7Re8^#eyP#~4UG(#qa1Dm)g4++KZcX3cMCk2EYQWhXcIdB^gA}FW9m^gNZ z9XN;(C-FdF5Mm<5DNIG6OtGxgq-Q-%QYe9E{R_|i`CKpV@0;tsUwjpu(49fy^DI&= zGMKS(f*3w?D}(91?{y}N!Q7X`7xKBhy-EDoxPN&7_hUhvnF|uQ$#Lf0&6CYy{F_(Z zS&U4^|1*nHCT>&H$rB_h#tp8A3SsY2328?qYR^`I^Nk+IUsNMnqlH(f7N({g7#E6A zAFIUEFd52~MYuA~L(zf~GL|JL>jtw)*$YqTbE;Xvy3S6?9BiHS8r1iRvD38hsd-zN0EziW+ zi)l~?(*?j~Ph zVhz2!@(j)7PZ5`{Y*H_;B+mYwbZQ`jE`B}+X{$8Y5~4w#tsXbl{y;*nT99e3idu5s zM9%$w>XaHp|NE$g*o5?wLk?_wD9@p5)g5Hpx0lJxZ%xGiD+|c45Cp8sLb&-><0U=F zd>x)F(|~r>5nC4vvh_a=G^aNffie%YjjTlB!He|PA4Ry3B7kS+Bht;QAR4PeIw2N9 zJ1)g%*ECQDYSELg!u>^hgy{>gJ64Dxp$&wRPkowm_fpP+tys!ij7>3ooD5Z9!H^uh zKrvR{=_2}`E;73@2dxbr*V;bQnT9{lG;FMt3E+* zy##M1NzunUhBjj#Ev;%N?L87o5-wBWqZPPvUPB#Qx1luq2>#5dr}kndcC86U;aE0) zvbUxRXCX2_d=HTkkBGzLc>1c1CG}pkAG6_7jJ%(UFq4pe(y1ZIU(}Eo`x4R?R7uAU zzm5^lh4{JnCVAiadlF)~3K1J}aqNx={eihCKDQQ^SFFLn6r29_%r$ar2N%P+dAR3L zgk4Ts@UWnc8a$q(R@xIpYu`V1v!D`gtfGHPyvWxI_ zaw`6DR^#(zJ1FB*an*kZ0zPuZsFi_ud|5}d*Vwr3dX@NWS%KgkFG7;P91%WmQFkJx zgX<6DC3gc7_En;@Ux(E{RAZV`iSI&mcqn(pv?&U_siiQv_(ISehRIuI|LjEA^x$t& z{q+V+9asXd1i+FX1k=1x%3?~ut|>>)i~jb~t>R?~`Aw zMA#)f2Coqvq%j8cdFMd&iU8K5Z-U7=h-kNH=;lt59p9>9=T(czxCUrjakO+pnMEGlqVWA1csLWo^00<#ndVID!j{Es1c9EH9t7W$bIx>c^k zfX)Elqas8Gou_*aUm}z2bL6hzS!{O-Kr&NOaC}-t?fS-O zRNw?XCRze6qlH|ao1`ZPKS1cu>)~j2;B?|<>N50%4oL!FeB%&U;bPo)QU+sy0UH_{ zu{oj}~C$XC~5U Date: Tue, 12 Apr 2022 16:46:13 +0200 Subject: [PATCH 12/22] run tests on all OS and update CI comment --- .github/workflows/run_demos_python.yml | 2 +- .github/workflows/run_tests_python.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml index a291f46..c32efb4 100644 --- a/.github/workflows/run_demos_python.yml +++ b/.github/workflows/run_demos_python.yml @@ -56,5 +56,5 @@ jobs: python -c "import sys; print(sys.version)" pip list - - name: Run tests and generate coverage report + - name: Run notebooks run: pytest --nbmake --nbmake-timeout=3000 "./examples" diff --git a/.github/workflows/run_tests_python.yml b/.github/workflows/run_tests_python.yml index 399a715..29732a8 100644 --- a/.github/workflows/run_tests_python.yml +++ b/.github/workflows/run_tests_python.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest, macOS-latest, windows-latest] python-version: ["3.7", "3.8", "3.9"] # fails on 3.10 fail-fast: true # cancel all jobs if one fails From e4cb2047ce172cfb91b2730a01e9cf82dfb4e2c4 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 16:46:28 +0200 Subject: [PATCH 13/22] lint --- tests/test_GLM_single.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_GLM_single.py b/tests/test_GLM_single.py index 374b7c5..62089ff 100644 --- a/tests/test_GLM_single.py +++ b/tests/test_GLM_single.py @@ -30,6 +30,7 @@ tmp = np.load(join(expected_dir, "TYPED_FITHRF_R2.npy"), allow_pickle=True) expected["typed"]["R2"] = tmp + def test_GLM_single_system(): X = sio.loadmat(data_file) @@ -58,12 +59,15 @@ def test_GLM_single_system(): tmp = np.load(join(output_dir, "python", "TYPEB_FITHRF.npy"), allow_pickle=True) results["typeb"]["HRFindex"] = tmp.item(0)["HRFindex"] - tmp = np.load(join(output_dir, "python", "TYPEC_FITHRF_GLMDENOISE.npy"), allow_pickle=True) + tmp = np.load( + join(output_dir, "python", "TYPEC_FITHRF_GLMDENOISE.npy"), allow_pickle=True + ) results["typec"]["HRFindex"] = tmp.item(0)["HRFindex"] - tmp = np.load(join(output_dir, "python", "TYPED_FITHRF_GLMDENOISE_RR.npy"), allow_pickle=True) + tmp = np.load( + join(output_dir, "python", "TYPED_FITHRF_GLMDENOISE_RR.npy"), allow_pickle=True + ) results["typed"]["HRFindex"] = tmp.item(0)["HRFindex"] - assert (results["typeb"]["HRFindex"] == expected["typeb"]["HRFindex"]).all assert (results["typec"]["HRFindex"] == expected["typec"]["HRFindex"]).all assert (results["typed"]["HRFindex"] == expected["typed"]["HRFindex"]).all From e1aa83b316977878e28a0a561c7d94742e1d666f Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 16:48:56 +0200 Subject: [PATCH 14/22] add dev doc --- CONTRIBUTING.md | 62 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index baaa794..43572ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Information for anyone who would like to contribute to this repository. ├── .git ├── .github │ └── workflows # Github continuous integration set up -├── examples +├── examples # Python demos: jupyter notebooks │ ├── data │ ├── example1outputs │ ├── example2outputs @@ -20,11 +20,11 @@ Information for anyone who would like to contribute to this repository. │ ├── ols │ ├── ssq │ └── utils -├── matlab # Matlab implementation -│ ├── examples +├── matlab # MATLAB implementation +│ ├── examples # MATLAB demos │ ├── fracridge │ └── utilities -└── tests # Python and Matlab tests +└── tests # Python and MATLAB tests └── data ``` @@ -47,10 +47,62 @@ Information for anyone who would like to contribute to this repository. ## Python +All the packages required to help with the python development of GLMsingle can +be installed with: + +```bash +pip install -r requirements_dev.txt +``` + ### Style guide +[black](https://black.readthedocs.io/en/stable/) (code formater) and +[flake8](https://flake8.pycqa.org/en/latest/) (style guide enforcement) are used +on the test code base. + +Ypu can use make to run them automatically with + +```bash +make lint/black # to run black +make lint/flake8 # to run flake8 +make lint # to run both +``` + ### Tests +The tests can be run with with pytest via the make command: + +```bash +make test-python +``` + #### Demos -### Continuous integration \ No newline at end of file +The jupyter notebook are tested with the +[`nbmake` plugin for pytest](https://pypi.org/project/nbmake/). + +They can be run with the make command: + +```bash +make test-notebooks +``` + +### Continuous integration + +We use Github to run several workflows for continuous integration. + +#### Tests + +The python tests are run by the workflow: +`.github/workflows/run_tests_python.yaml`. + +Those tests should be run with every push on the `master` branch and on pull +request that target the `master` branch. + +#### Demos + +The demos in the `examples` folder are run automatically in Github CI at regular +intervals. + +The jupyter notebooks are run by the workflow +`.github/workflows/run_demos_python.yaml`. From f76552cce69d79f47adc2b6a89b3f10f43509095 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 16:50:30 +0200 Subject: [PATCH 15/22] run demos on ubuntu latest in CI --- .github/workflows/run_demos_python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml index c32efb4..5a9ebdb 100644 --- a/.github/workflows/run_demos_python.yml +++ b/.github/workflows/run_demos_python.yml @@ -30,7 +30,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest] python-version: ["3.9"] steps: From a27c0aa5b0f13e4acb847fae5ea12e909c955665 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 16:57:29 +0200 Subject: [PATCH 16/22] add badges --- .github/workflows/run_tests_python.yml | 2 +- README.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_tests_python.yml b/.github/workflows/run_tests_python.yml index 29732a8..027da1e 100644 --- a/.github/workflows/run_tests_python.yml +++ b/.github/workflows/run_tests_python.yml @@ -1,4 +1,4 @@ -name: Python test +name: Python tests on: push: diff --git a/README.md b/README.md index 336ed01..47a23e0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ + + +[![Python tests](https://github.com/cvnlab/GLMsingle/actions/workflows/run_tests_python.yml/badge.svg)](https://github.com/cvnlab/GLMsingle/actions/workflows/run_tests_python.yml) +[![Python demos](https://github.com/cvnlab/GLMsingle/actions/workflows/run_demos_python.yml/badge.svg)](https://github.com/cvnlab/GLMsingle/actions/workflows/run_demos_python.yml) +[![Python-coverage](https://codecov.io/gh/Remi-Gau/GLMsingle/branch/main/graph/badge.svg?token=H75TAAUVSW)](https://codecov.io/gh/Remi-Gau/GLMsingle) # GLMsingle ![image](https://user-images.githubusercontent.com/35503086/151108958-24479034-c7f7-4734-b903-9046ba6a78ac.png) From 66a5d19acec3bd46ea623a34fd30039ad7158e45 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 17:01:03 +0200 Subject: [PATCH 17/22] test a forced failure of demos --- examples/example1.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/example1.ipynb b/examples/example1.ipynb index b7fc301..11e1262 100644 --- a/examples/example1.ipynb +++ b/examples/example1.ipynb @@ -85,7 +85,9 @@ "\n", "# note: the fracridge repository is also necessary to run this code\n", "# for example, you could do:\n", - "# git clone https://github.com/nrdg/fracridge.git" + "# git clone https://github.com/nrdg/fracridge.git\n", + "\n", + "raise NameError('test')" ] }, { From 0331ef52527664670adfef56da19712e9284b43e Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 17:23:02 +0200 Subject: [PATCH 18/22] only run tests on push on main --- .github/workflows/run_tests_python.yml | 2 +- examples/example2.ipynb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests_python.yml b/.github/workflows/run_tests_python.yml index 027da1e..39f8b39 100644 --- a/.github/workflows/run_tests_python.yml +++ b/.github/workflows/run_tests_python.yml @@ -2,7 +2,7 @@ name: Python tests on: push: - branches: ["*"] + branches: ["main"] pull_request: branches: ["*"] diff --git a/examples/example2.ipynb b/examples/example2.ipynb index b21dba6..dafb7d5 100644 --- a/examples/example2.ipynb +++ b/examples/example2.ipynb @@ -56,7 +56,9 @@ "\n", "# note: the fracridge repository is also necessary to run this code\n", "# for example, you could do:\n", - "# git clone https://github.com/nrdg/fracridge.git" + "# git clone https://github.com/nrdg/fracridge.git\n", + "\n", + "raise NameError('test')" ] }, { From 71c3a539d8917f659f7f0b81eeb676b3a38bf6cc Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 17:35:10 +0200 Subject: [PATCH 19/22] revert forced demo failure --- examples/example1.ipynb | 4 +--- examples/example2.ipynb | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/example1.ipynb b/examples/example1.ipynb index 11e1262..8fc7552 100644 --- a/examples/example1.ipynb +++ b/examples/example1.ipynb @@ -85,9 +85,7 @@ "\n", "# note: the fracridge repository is also necessary to run this code\n", "# for example, you could do:\n", - "# git clone https://github.com/nrdg/fracridge.git\n", - "\n", - "raise NameError('test')" + "# git clone https://github.com/nrdg/fracridge.git\n" ] }, { diff --git a/examples/example2.ipynb b/examples/example2.ipynb index dafb7d5..c5dbfe5 100644 --- a/examples/example2.ipynb +++ b/examples/example2.ipynb @@ -56,9 +56,7 @@ "\n", "# note: the fracridge repository is also necessary to run this code\n", "# for example, you could do:\n", - "# git clone https://github.com/nrdg/fracridge.git\n", - "\n", - "raise NameError('test')" + "# git clone https://github.com/nrdg/fracridge.git\n" ] }, { From 14d83d948417beebb25468e6910b0f24b77a5d47 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 12 Apr 2022 17:42:34 +0200 Subject: [PATCH 20/22] only run demos in CI on schedule --- .github/workflows/run_demos_python.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/run_demos_python.yml b/.github/workflows/run_demos_python.yml index 5a9ebdb..3c700a9 100644 --- a/.github/workflows/run_demos_python.yml +++ b/.github/workflows/run_demos_python.yml @@ -21,9 +21,6 @@ on: schedule: - cron: "0 0 1,15 * *" - push: - branches: ["*"] - jobs: demos: continue-on-error: true From a972d94759d9136df68eff81b7517a034afa5f59 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 13 Apr 2022 09:02:46 +0200 Subject: [PATCH 21/22] add test on R2 --- tests/test_GLM_single.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_GLM_single.py b/tests/test_GLM_single.py index 62089ff..94fdb34 100644 --- a/tests/test_GLM_single.py +++ b/tests/test_GLM_single.py @@ -71,3 +71,4 @@ def test_GLM_single_system(): assert (results["typeb"]["HRFindex"] == expected["typeb"]["HRFindex"]).all assert (results["typec"]["HRFindex"] == expected["typec"]["HRFindex"]).all assert (results["typed"]["HRFindex"] == expected["typed"]["HRFindex"]).all + assert (results["typed"]["R2"] == expected["typed"]["R2"]).all From 9daa42da85200c3a34843dde56e2d888531842de Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 13 Apr 2022 09:16:22 +0200 Subject: [PATCH 22/22] fix test --- tests/test_GLM_single.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_GLM_single.py b/tests/test_GLM_single.py index 94fdb34..b644eec 100644 --- a/tests/test_GLM_single.py +++ b/tests/test_GLM_single.py @@ -67,6 +67,7 @@ def test_GLM_single_system(): join(output_dir, "python", "TYPED_FITHRF_GLMDENOISE_RR.npy"), allow_pickle=True ) results["typed"]["HRFindex"] = tmp.item(0)["HRFindex"] + results["typed"]["R2"] = tmp.item(0)["R2"] assert (results["typeb"]["HRFindex"] == expected["typeb"]["HRFindex"]).all assert (results["typec"]["HRFindex"] == expected["typec"]["HRFindex"]).all