From 4f40c493a1f6f86d4117b6c498c24638d50ab18e Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 2 Aug 2024 18:19:20 +0200 Subject: [PATCH] [ENH] turn `--action` into subcommands (#215) * speed up CLI and pin docker dependencies * add more flake8 * update CLI * split parser and cli * use sub commands * fix doc * fix imports * update linting * fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix * fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix * fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .circleci/config.yml | 2 +- .flake8 | 14 +- .github/workflows/system_tests.yml | 4 + .pre-commit-config.yaml | 19 +- Dockerfile | 9 +- Makefile | 38 +- README.md | 28 +- bidsmreye/_cli.py | 71 +++ bidsmreye/_parsers.py | 239 ++++++++ bidsmreye/bidsmreye.py | 217 +------ bidsmreye/configuration.py | 3 +- bidsmreye/defaults.py | 5 - bidsmreye/download.py | 53 +- bidsmreye/generalize.py | 4 +- bidsmreye/methods.py | 6 +- docs/source/demo.md | 4 +- docs/source/usage_notes.rst | 2 +- pyproject.toml | 27 +- requirements.txt | 573 +++++++++++++++++++ tests/__init__.py | 1 + tests/data/.gitignore | 1 + tests/test_download.py | 19 +- tests/test_methods.py | 3 - tests/{test_bidsmreye.py => test_parsers.py} | 23 +- tests/test_utils.py | 6 +- tox.ini | 36 ++ 26 files changed, 1034 insertions(+), 373 deletions(-) create mode 100644 bidsmreye/_cli.py create mode 100644 bidsmreye/_parsers.py create mode 100644 requirements.txt rename tests/{test_bidsmreye.py => test_parsers.py} (65%) create mode 100644 tox.ini diff --git a/.circleci/config.yml b/.circleci/config.yml index d0f234d..d9c92b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -95,10 +95,10 @@ jobs: /bids_dataset \ /outputs \ participant \ + all \ --participant_label 302 307 \ --space MNI152NLin2009cAsym \ --reset_database \ - --action all \ -vv no_output_timeout: 6h - store_artifacts: diff --git a/.flake8 b/.flake8 index eb4a51e..db1a275 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,4 @@ [flake8] -max-line-length = 90 count = True show-source = True statistics = True @@ -7,13 +6,10 @@ exclude = *build .git __pycache__ - tests/* _version.py - versioneer.py - tests_.*.py - version.*.py - setup.py -max-complexity = 10 ignore = D100, D103, W503 -per-file-ignores = - setup.py:E121 +max-line-length = 90 +max_complexity = 15 +max_function_length = 150 +max_parameters_amount = 15 +max_returns_amount = 5 diff --git a/.github/workflows/system_tests.yml b/.github/workflows/system_tests.yml index b445a93..4e2997e 100644 --- a/.github/workflows/system_tests.yml +++ b/.github/workflows/system_tests.yml @@ -11,6 +11,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Force to use color +env: + FORCE_COLOR: true + jobs: system_test: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb51f29..cca8e0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,11 +32,6 @@ repos: - id: pyupgrade args: [--py38-plus] -- repo: https://github.com/ikamensh/flynt/ - rev: 1.0.1 - hooks: - - id: flynt - - repo: https://github.com/seddonym/import-linter rev: v2.0 hooks: @@ -46,7 +41,7 @@ repos: rev: 5.13.2 hooks: - id: isort - args: [--profile, black] + args: [--settings-path, pyproject.toml] - repo: https://github.com/adamchainz/blacken-docs rev: 1.18.0 @@ -59,7 +54,7 @@ repos: rev: 24.4.2 hooks: - id: black - args: [--config=pyproject.toml, --verbose] + args: [--config=pyproject.toml] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.11.0 @@ -76,19 +71,13 @@ repos: args: [--toml=pyproject.toml] additional_dependencies: [tomli] -- repo: https://github.com/jendrikseipp/vulture - rev: v2.11 - hooks: - - id: vulture - - repo: https://github.com/pycqa/flake8 rev: 7.1.0 hooks: - id: flake8 exclude: tests_.*.py|version.*.py|setup.py # ignore tests and versioneer related code - args: [--verbose] - additional_dependencies: [flake8-docstrings] - + args: [--config, .flake8, --verbose] + additional_dependencies: [flake8-docstrings, flake8-use-fstring, flake8-functions, flake8-bugbear] ci: autoupdate_commit_msg: 'chore: update pre-commit hooks' diff --git a/Dockerfile b/Dockerfile index 2982add..d1a1fc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.9-bullseye +FROM python:3.11.9-slim-bullseye@sha256:8850f5e6e8da9081a6d156252a11161aa22f04d6ed1723c57ca2d5a5d48132bc ARG DEBIAN_FRONTEND="noninteractive" @@ -12,11 +12,12 @@ WORKDIR /home/neuro/bidsMReye COPY [".", "/home/neuro/bidsMReye"] RUN pip install --upgrade pip && \ - pip3 install -e . + pip3 install -r requirements.txt && \ + pip3 install . RUN bidsmreye_model -ENTRYPOINT [ "//home/neuro/bidsMReye/entrypoint.sh" ] +ENTRYPOINT [ "/home/neuro/bidsMReye/entrypoint.sh" ] COPY ["./docker/entrypoint.sh", \ - "//home/neuro/bidsMReye/entrypoint.sh"] + "/home/neuro/bidsMReye/entrypoint.sh"] RUN chmod +x /home/neuro/bidsMReye/entrypoint.sh diff --git a/Makefile b/Makefile index 63c6891..83002d0 100644 --- a/Makefile +++ b/Makefile @@ -84,18 +84,6 @@ models/dataset4_pursuit.h5: models/dataset5_free_viewing.h5: bidsmreye_model --model_name 5_free_viewing - -## STYLE - -lint/flake8: ## check style with flake8 - flake8 bidsmreye tests -lint/black: ## check style with black - black bidsmreye tests -lint/mypy: ## check style with mypy - mypy bidsmreye - -lint: lint/black lint/mypy lint/flake8 ## check style - ## DOC .PHONY: docs docs/source/FAQ.md @@ -139,7 +127,7 @@ prepare: tests/data/moae_fmriprep ## demo: prepares the data of MOAE dataset bidsmreye $$PWD/tests/data/moae_fmriprep \ $$PWD/outputs/moae_fmriprep/derivatives \ participant \ - --action prepare \ + prepare \ -vv \ --reset_database \ --non_linear_coreg @@ -148,12 +136,11 @@ generalize: ## demo: predicts labels of MOAE dataset bidsmreye $$PWD/tests/data/moae_fmriprep \ $$PWD/outputs/moae_fmriprep/derivatives \ participant \ - --action generalize \ - -vv \ - --non_linear_coreg + generalize \ + -vv -## Openneuro data +## ds002799 .PHONY: get_ds002799_dat clean-ds002799: @@ -170,7 +157,7 @@ ds002799_prepare: get_ds002799 bidsmreye $$PWD/tests/data/ds002799/derivatives/fmriprep \ $$PWD/outputs/ds002799/derivatives \ participant \ - --action prepare \ + prepare \ --participant_label 302 307 \ --space MNI152NLin2009cAsym \ --reset_database \ @@ -181,7 +168,7 @@ ds002799_generalize: bidsmreye $$PWD/tests/data/ds002799/derivatives/fmriprep \ $$PWD/outputs/ds002799/derivatives \ participant \ - --action generalize \ + generalize \ --participant_label 302 307 \ --space MNI152NLin2009cAsym \ --run 1 2 @@ -191,13 +178,18 @@ ds002799: clean-ds002799 get_ds002799 bidsmreye $$PWD/tests/data/ds002799/derivatives/fmriprep \ $$PWD/outputs/ds002799/derivatives \ participant \ - --action all \ + all \ --participant_label 302 307 \ --space MNI152NLin2009cAsym \ --run 1 2 \ --reset_database \ -vv +## ds002799 +get_ds000114: + datalad install -s ///openneuro-derivatives/ds000114-fmriprep tests/data/ds000114-fmriprep + cd tests/data/ds000114-fmriprep && datalad get sub-0[1-2]/ses-*/func/*MNI*desc-preproc*bold.nii.gz -J 12 + ## DOCKER .PHONY: @@ -219,7 +211,7 @@ docker_prepare_data: /home/neuro/data/ \ /home/neuro/outputs/ \ participant \ - --action prepare \ + prepare \ --reset_database docker_generalize: @@ -230,7 +222,7 @@ docker_generalize: /home/neuro/data/ \ /home/neuro/outputs/ \ participant \ - --action generalize + generalize docker_ds002799: get_ds002799 # datalad unlock $$PWD/tests/data/ds002799/derivatives/fmriprep/sub-30[27]/ses-*/func/*run-*preproc*bold* @@ -241,7 +233,7 @@ docker_ds002799: get_ds002799 /home/neuro/data/ \ /home/neuro/outputs/ \ participant \ - --action all \ + all \ --participant_label 302 307 \ --space MNI152NLin2009cAsym \ --run 1 2 \ diff --git a/README.md b/README.md index 5f2bf15..4cd0cd6 100644 --- a/README.md +++ b/README.md @@ -156,48 +156,38 @@ bidsmreye --help ## Preparing the data -`--action prepapre` means that bidsmreye will extract the data coming from the +`prepapre` means that bidsmreye will extract the data coming from the eyes from the fMRI images. If your data is not in MNI space, bidsmreye will also register the data to MNI. ```bash -bidsmreye --action prepare \ - bids_dir \ - output_dir \ - participant +bidsmreye bids_dir output_dir participant prepare ``` ## Computing the eye movements -`--action generalize` use the extracted timeseries to predict the eye movements +`generalize` use the extracted timeseries to predict the eye movements using the default pre-trained model of deepmreye. This will also generate a quality control report of the decoded eye movements. ```bash -bidsmreye --action generalize \ - bids_dir \ - output_dir \ - participant +bidsmreye bids_dir output_dir participant generalize ``` ## Doing it all at once -`--action all` does "prepare" then "generalize". +`all` does "prepare" then "generalize". ```bash -bidsmreye --action all \ - bids_dir \ - output_dir \ - participant +bidsmreye bids_dir output_dir participant all ``` ## Group level summary -bidsmreye --action qc \ - bids_dir \ - output_dir \ - group +``` +bidsmreye bids_dir output_dir group qc +``` ## Demo diff --git a/bidsmreye/_cli.py b/bidsmreye/_cli.py new file mode 100644 index 0000000..1af88b2 --- /dev/null +++ b/bidsmreye/_cli.py @@ -0,0 +1,71 @@ +"""Define the command line interface.""" + +from __future__ import annotations + +import sys +from typing import Any + +from rich_argparse import RichHelpFormatter + +from bidsmreye._parsers import common_parser, download_parser +from bidsmreye.bidsmreye import bidsmreye +from bidsmreye.defaults import default_log_level, log_levels +from bidsmreye.download import download +from bidsmreye.logging import bidsmreye_log + +log = bidsmreye_log(name="bidsmreye") + + +def cli(argv: Any = sys.argv) -> None: + """Run the bids app. + + :param argv: _description_, defaults to sys.argv + :type argv: _type_, optional + """ + parser = common_parser(formatter_class=RichHelpFormatter) + + args = parser.parse_args(argv[1:]) + + log.debug(f"args:\n{args}") + + # TODO integrate as part of base config + # https://stackoverflow.com/a/53293042/14223310 + log_level = log_levels().index(default_log_level()) + # For each "-v" flag, adjust the logging verbosity accordingly + # making sure to clamp off the value from 0 to 4, inclusive of both + for adjustment in args.log_level or (): + log_level = min(len(log_levels()) - 1, max(log_level + adjustment, 0)) + log_level_name = log_levels()[log_level] + + model_weights_file = None + if getattr(args, "model", None) is not None: + model_weights_file = str(getattr(args, "model")) + + bidsmreye( + bids_dir=args.bids_dir[0], + output_dir=args.output_dir[0], + analysis_level=args.analysis_level[0], + action=args.command, + participant_label=args.participant_label or None, + space=args.space or None, + task=args.task or None, + run=args.run or None, + debug=args.debug, + model_weights_file=model_weights_file, + reset_database=args.reset_database, + bids_filter_file=args.bids_filter_file, + non_linear_coreg=bool(getattr(args, "non_linear_coreg", False)), + log_level_name=log_level_name, + ) + + +def cli_download(argv: Any = sys.argv) -> None: + """Download the models from OSF. + + :return: _description_ + :rtype: _type_ + """ + parser = download_parser(formatter_class=RichHelpFormatter) + args = parser.parse_args(argv[1:]) + + download(model_name=args.model_name, output_dir=args.output_dir) diff --git a/bidsmreye/_parsers.py b/bidsmreye/_parsers.py new file mode 100644 index 0000000..531c0ef --- /dev/null +++ b/bidsmreye/_parsers.py @@ -0,0 +1,239 @@ +from __future__ import annotations + +from argparse import ArgumentParser, HelpFormatter +from pathlib import Path + +from bidsmreye._version import __version__ +from bidsmreye.defaults import available_models, default_model + + +def _base_parser(formatter_class: type[HelpFormatter] = HelpFormatter) -> ArgumentParser: + parser = ArgumentParser( + description=( + "BIDS app using deepMReye to decode " "eye motion for fMRI time series data." + ), + epilog=""" + For a more readable version of this help section, + see the online https://bidsmreye.readthedocs.io/. + """, + formatter_class=formatter_class, + ) + parser.add_argument( + "--version", + action="version", + help="show program's version number and exit", + version=f"\nbidsMReye version {__version__}\n", + ) + parser.add_argument( + "bids_dir", + help=""" +Fullpath to the directory with the input dataset +formatted according to the BIDS standard. +The dataset should contain 'minimally processed' +(meaning at least 'realigned'). +The output of fMRIprep or bidspm should be acceptable inputs. + +For "qc", this lust be the fullpath to the directory +with the output generated by bidsmreye +that should be quality controlled. + """, + nargs=1, + ) + parser.add_argument( + "output_dir", + help=""" + Fullpath to the directory where the output files will be stored. + """, + nargs=1, + ) + parser.add_argument( + "analysis_level", + help=""" + Level of the analysis that will be performed. + Multiple participant level analyses can be run independently + (in parallel) using the same ``output_dir``. + """, + choices=["participant", "group"], + default="participant", + type=str, + nargs=1, + ) + + return parser + + +def _add_common_arguments(parser: ArgumentParser) -> ArgumentParser: + parser.add_argument( + "--participant_label", + help=""" +The label(s) of the participant(s) that should be analyzed. +The label corresponds to sub- from the BIDS spec +(so it does not include "sub-"). + +If this parameter is not provided, all subjects will be analyzed. +Multiple participants can be specified with a space separated list. + """, + nargs="+", + ) + parser.add_argument( + "--task", + help=""" +The label of the task that will be analyzed. + +The label corresponds to task- from the BIDS spec +so it does not include "task-"). + """, + nargs="+", + ) + parser.add_argument( + "--run", + help=""" +The label of the run that will be analyzed. + +The label corresponds to run- from the BIDS spec +so it does not include "run-"). + """, + nargs="+", + ) + parser.add_argument( + "--space", + help=""" +The label of the space that will be analyzed. + +The label corresponds to space- from the BIDS spec +(so it does not include "space-"). + """, + nargs="+", + ) + parser.add_argument( + "-v", + "--verbose", + dest="log_level", + action="append_const", + const=-1, + ) + parser.add_argument("--debug", help="Switch to debug mode", action="store_true") + parser.add_argument( + "--reset_database", + help=""" +Resets the database of the input dataset. + """, + action="store_true", + ) + parser.add_argument( + "--bids_filter_file", + help=""" +A JSON file describing custom BIDS input filters using PyBIDS. +For further details, please check out TBD. + """, + ) + return parser + + +def common_parser(formatter_class: type[HelpFormatter] = HelpFormatter) -> ArgumentParser: + """Execute the main script.""" + parser = _base_parser(formatter_class=formatter_class) + subparsers = parser.add_subparsers( + dest="command", + help="Choose a subcommand", + required=True, + ) + + prepare_parser = subparsers.add_parser( + "prepare", + help=""" +Preprocessing data for classification. +Extract the data coming from the eyes from the fMRI images. +If your data is not in MNI space, bidsmreye will also register the data to MNI. +""", + formatter_class=parser.formatter_class, + ) + prepare_parser = _add_common_arguments(prepare_parser) + # TODO make it possible to pass path to a model ? + prepare_parser.add_argument( + "--non_linear_coreg", + help=""" +Uses a more aggressive (and non-linear) alignment procedure +to the deepmreye template.""", + action="store_true", + ) + + generalize_parser = subparsers.add_parser( + "generalize", + help="""Run classification. +Use the extracted timeseries to predict the eye movements +using the default pre-trained model of deepmreye. + """, + formatter_class=parser.formatter_class, + ) + generalize_parser = _add_common_arguments(generalize_parser) + # TODO make it possible to pass path to a model ? + generalize_parser.add_argument( + "--model", + help="model to use", + choices=available_models(), + default=default_model(), + ) + + all_parser = subparsers.add_parser( + "all", + help="""Run prepare, generalize.""", + formatter_class=parser.formatter_class, + ) + all_parser = _add_common_arguments(all_parser) + # TODO make it possible to pass path to a model ? + all_parser.add_argument( + "--non_linear_coreg", + help=""" +Uses a more aggressive (and non-linear) alignment procedure +to the deepmreye template. +""", + action="store_true", + ) + # TODO make it possible to pass path to a model ? + all_parser.add_argument( + "--model", + help="model to use", + choices=available_models(), + default=default_model(), + ) + + qc_parser = subparsers.add_parser( + "qc", + help="""Run quality control on output.""", + formatter_class=parser.formatter_class, + ) + qc_parser = _add_common_arguments(qc_parser) + + return parser + + +def download_parser( + formatter_class: type[HelpFormatter] = HelpFormatter, +) -> ArgumentParser: + """Execute the main script.""" + parser = ArgumentParser( + description="Download deepmreye pretrained model from OSF.", + epilog=""" +For a more readable version of this help section, +see the online https://bidsmreye.readthedocs.io/. + """, + formatter_class=formatter_class, + ) + parser.add_argument( + "--model_name", + help=""" +Model to download. + """, + choices=available_models(), + default=default_model(), + ) + parser.add_argument( + "--output_dir", + help=""" +The directory where the model files will be stored. + """, + default=Path.cwd().joinpath("models"), + ) + + return parser diff --git a/bidsmreye/bidsmreye.py b/bidsmreye/bidsmreye.py index 6610cea..6080b11 100755 --- a/bidsmreye/bidsmreye.py +++ b/bidsmreye/bidsmreye.py @@ -2,72 +2,18 @@ """Main script.""" from __future__ import annotations -import argparse import json import sys from pathlib import Path -from typing import IO, Any - -import rich from bidsmreye._version import __version__ from bidsmreye.configuration import Config -from bidsmreye.defaults import ( - allowed_actions, - available_models, - default_log_level, - default_model, - log_levels, -) -from bidsmreye.download import download -from bidsmreye.generalize import generalize +from bidsmreye.defaults import default_log_level from bidsmreye.logging import bidsmreye_log -from bidsmreye.prepare_data import prepare_data -from bidsmreye.quality_control import quality_control_input -from bidsmreye.visualize import group_report log = bidsmreye_log(name="bidsmreye") -def cli(argv: Any = sys.argv) -> None: - """Run the bids app. - - :param argv: _description_, defaults to sys.argv - :type argv: _type_, optional - """ - parser = common_parser() - - args = parser.parse_args(argv[1:]) - - log.debug(f"args:\n{args}") - - # TODO integrate as part of base config - # https://stackoverflow.com/a/53293042/14223310 - log_level = log_levels().index(default_log_level()) - # For each "-v" flag, adjust the logging verbosity accordingly - # making sure to clamp off the value from 0 to 4, inclusive of both - for adjustment in args.log_level or (): - log_level = min(len(log_levels()) - 1, max(log_level + adjustment, 0)) - log_level_name = log_levels()[log_level] - - bidsmreye( - bids_dir=args.bids_dir, - output_dir=args.output_dir, - analysis_level=args.analysis_level, - action=args.action, - participant_label=args.participant_label or None, - space=args.space or None, - task=args.task or None, - run=args.run or None, - debug=args.debug, - model_weights_file=args.model or None, - reset_database=args.reset_database, - bids_filter_file=args.bids_filter_file, - non_linear_coreg=args.non_linear_coreg, - log_level_name=log_level_name, - ) - - def bidsmreye( bids_dir: str, output_dir: str, @@ -82,7 +28,7 @@ def bidsmreye( reset_database: bool | None = None, bids_filter_file: str | None = None, non_linear_coreg: bool = False, - log_level_name: str = default_log_level(), + log_level_name: str | None = None, ) -> None: bids_filter = None if bids_filter_file is not None and Path(bids_filter_file).is_file(): @@ -106,6 +52,8 @@ def bidsmreye( non_linear_coreg=non_linear_coreg, ) # type: ignore + if log_level_name is None: + log_level_name = default_log_level() log.setLevel(log_level_name) print(log_level_name) @@ -120,8 +68,11 @@ def bidsmreye( log.debug("DEBUG MODE") log.debug(f"Configuration:\n{cfg}") + log.debug(f"{analysis_level=} {action=}") if action in {"all", "generalize"} and isinstance(cfg.model_weights_file, str): + from bidsmreye.download import download + cfg.model_weights_file = download(cfg.model_weights_file) dispatch(analysis_level=analysis_level, action=action, cfg=cfg) @@ -130,6 +81,8 @@ def bidsmreye( def dispatch(analysis_level: str, action: str, cfg: Config) -> None: if analysis_level == "group": if action == "qc": + from bidsmreye.visualize import group_report + group_report(cfg=cfg) else: log.error("Unknown group level action") @@ -137,157 +90,23 @@ def dispatch(analysis_level: str, action: str, cfg: Config) -> None: elif analysis_level == "participant": if action == "all": + from bidsmreye.generalize import generalize + from bidsmreye.prepare_data import prepare_data + prepare_data(cfg) generalize(cfg) elif action == "prepare": + from bidsmreye.prepare_data import prepare_data + prepare_data(cfg) elif action == "generalize": + from bidsmreye.generalize import generalize + generalize(cfg) elif action == "qc": + from bidsmreye.quality_control import quality_control_input + quality_control_input(cfg) else: log.error("Unknown participant level action") sys.exit(1) - - -class MuhParser(argparse.ArgumentParser): - """Parser for the main script.""" - - def _print_message(self, message: str, file: IO[str] | None = None) -> None: - rich.print(message, file=file) - - -def common_parser() -> MuhParser: - """Execute the main script.""" - parser = MuhParser( - description=( - "BIDS app using deepMReye to decode " "eye motion for fMRI time series data." - ), - epilog=""" - For a more readable version of this help section, - see the online https://bidsmreye.readthedocs.io/. - """, - ) - parser.add_argument( - "bids_dir", - help=( - "The directory with the input dataset " - "formatted according to the BIDS standard." - ), - ) - parser.add_argument( - "output_dir", - help=""" - The directory where the output files will be stored. - """, - ) - parser.add_argument( - "analysis_level", - help="""Level of the analysis that will be performed. - Multiple participant level analyses can be run independently (in parallel) - using the same output_dir. - """, - choices=["participant", "group"], - default="participant", - ) - parser.add_argument( - "--action", - help=""" - What action to perform. - """, - choices=allowed_actions(), - default="all", - ) - parser.add_argument( - "--participant_label", - help=""" - The label(s) of the participant(s) that should be analyzed. - The label corresponds to sub- from the BIDS spec - (so it does not include "sub-"). - - If this parameter is not provided, all subjects will be analyzed. - Multiple participants can be specified with a space separated list. - """, - nargs="+", - ) - parser.add_argument( - "--task", - help=""" - The label of the task that will be analyzed. - - The label corresponds to task- from the BIDS spec - so it does not include "task-"). - """, - nargs="+", - ) - parser.add_argument( - "--run", - help=""" - The label of the run that will be analyzed. - - The label corresponds to run- from the BIDS spec - so it does not include "run-"). - """, - nargs="+", - ) - parser.add_argument( - "--space", - help=""" - The label of the space that will be analyzed. - - The label corresponds to space- from the BIDS spec - (so it does not include "space-"). - """, - nargs="+", - ) - parser.add_argument( - "-v", - "--verbose", - dest="log_level", - action="append_const", - const=-1, - ) - parser.add_argument("--debug", help="Switch to debug mode", action="store_true") - parser.add_argument( - "--reset_database", - help=""" - Resets the database of the input dataset. - """, - action="store_true", - ) - parser.add_argument( - "--bids_filter_file", - help=""" - A JSON file describing custom BIDS input filters using PyBIDS. - For further details, please check out TBD. - """, - ) - parser.add_argument( - "--version", - action="version", - help="show program's version number and exit", - version=f"\nbidsMReye version {__version__}\n", - ) - # TODO make it possible to pass path to a model ? - prepare_only = parser.add_argument_group("prepare only arguments") - prepare_only.add_argument( - "--non_linear_coreg", - help="Uses a more aggressive (and non-linear) alignment procedure " - "to the deepmreye template.", - action="store_true", - ) - # TODO make it possible to pass path to a model ? - generalize_only = parser.add_argument_group("generalize only arguments") - generalize_only.add_argument( - "--model", - help="model to use", - choices=available_models(), - default=default_model(), - ) - - return parser - - -def args_to_dict(args: argparse.Namespace) -> dict[str, Any]: - """Convert a argparse.Namespace object to a dictionary.""" - return vars(args) diff --git a/bidsmreye/configuration.py b/bidsmreye/configuration.py index 1dee9d0..61c1def 100644 --- a/bidsmreye/configuration.py +++ b/bidsmreye/configuration.py @@ -142,7 +142,8 @@ def check_argument(self, attribute: str, layout_in: BIDSLayout) -> Config: if getattr(self, attribute): if missing_values := list(set(getattr(self, attribute)) - set(value)): warnings.warn( - f"{attribute}(s) {missing_values} not found in {self.input_dir}" + f"{attribute}(s) {missing_values} not found in {self.input_dir}", + stacklevel=3, ) value = list(set(getattr(self, attribute)) & set(value)) diff --git a/bidsmreye/defaults.py b/bidsmreye/defaults.py index 913cd4e..830359d 100644 --- a/bidsmreye/defaults.py +++ b/bidsmreye/defaults.py @@ -3,11 +3,6 @@ from __future__ import annotations -def allowed_actions() -> list[str]: - """Return a list of allowed actions.""" - return ["all", "prepare", "generalize", "qc"] - - def default_log_level() -> str: """Return default log level.""" return "WARNING" diff --git a/bidsmreye/download.py b/bidsmreye/download.py index 617a47c..1476b9b 100644 --- a/bidsmreye/download.py +++ b/bidsmreye/download.py @@ -2,15 +2,11 @@ from __future__ import annotations -import argparse -import sys import warnings from importlib import resources from pathlib import Path -from typing import IO, Any import pooch -import rich import bidsmreye from bidsmreye.defaults import available_models, default_model @@ -19,53 +15,6 @@ log = bidsmreye_log(name="bidsmreye") -class MuhParser(argparse.ArgumentParser): - """Parser for the main script.""" - - def _print_message(self, message: str, file: IO[str] | None = None) -> None: - rich.print(message, file=file) - - -def download_parser() -> MuhParser: - """Execute the main script.""" - parser = MuhParser( - description="Download deepmreye pretrained model from OSF.", - epilog=""" - For a more readable version of this help section, - see the online https://bidsmreye.readthedocs.io/. - """, - ) - parser.add_argument( - "--model_name", - help=""" - Model to download. - """, - choices=available_models(), - default=default_model(), - ) - parser.add_argument( - "--output_dir", - help=""" - The directory where the model files will be stored. - """, - default=Path.cwd().joinpath("models"), - ) - - return parser - - -def cli(argv: Any = sys.argv) -> None: - """Download the models from OSF. - - :return: _description_ - :rtype: _type_ - """ - parser = download_parser() - args = parser.parse_args(argv[1:]) - - download(model_name=args.model_name, output_dir=args.output_dir) - - def download( model_name: str | Path | None = None, output_dir: Path | str | None = None ) -> Path | None: @@ -86,7 +35,7 @@ def download( assert model_name.is_file() return model_name.resolve() if model_name not in available_models(): - warnings.warn(f"{model_name} is not a valid model name.") + warnings.warn(f"{model_name} is not a valid model name.", stacklevel=3) return None if output_dir is None: diff --git a/bidsmreye/generalize.py b/bidsmreye/generalize.py index dfe52bb..b2c70a5 100644 --- a/bidsmreye/generalize.py +++ b/bidsmreye/generalize.py @@ -176,7 +176,7 @@ def process_subject(cfg: Config, layout_out: BIDSLayout, subject_label: str) -> opts = model_opts.get_opts() - (model, model_inference) = train.train_model( + (_, model_inference) = train.train_model( dataset="example_data", generators=generators, opts=opts, @@ -190,7 +190,7 @@ def process_subject(cfg: Config, layout_out: BIDSLayout, subject_label: str) -> elif log.isEnabledFor(logging.INFO): verbose = 1 - (evaluation, scores) = train.evaluate_model( + (_, _) = train.evaluate_model( dataset="tmp", model=model_inference, generators=generators, diff --git a/bidsmreye/methods.py b/bidsmreye/methods.py index a5a264e..8c95a79 100644 --- a/bidsmreye/methods.py +++ b/bidsmreye/methods.py @@ -14,7 +14,7 @@ def methods( - output_dir: str | Path = Path("."), + output_dir: str | Path | None = None, model_name: str | None = None, qc_only: bool = False, ) -> Path: @@ -29,6 +29,8 @@ def methods( :return: Output file name. :rtype: Path """ + if output_dir is None: + output_dir = Path(".") if isinstance(output_dir, str): output_dir = Path(output_dir) output_dir = output_dir.joinpath("logs") @@ -50,7 +52,7 @@ def methods( is_default_model = True if not is_known_models: - warnings.warn(f"{model_name} is not a known model name.") + warnings.warn(f"{model_name} is not a known model name.", stacklevel=3) template_file = str(Path(__file__).parent.joinpath("templates", "CITATION.mustache")) with open(template_file) as template: diff --git a/docs/source/demo.md b/docs/source/demo.md index 83f738e..842ebc3 100644 --- a/docs/source/demo.md +++ b/docs/source/demo.md @@ -43,7 +43,7 @@ docker run --rm -it \ /home/neuro/data/ \ /home/neuro/outputs/ \ participant \ - --action prepare + prepare ``` ## Computing the eye movements @@ -60,5 +60,5 @@ docker run --rm -it \ /home/neuro/data/ \ /home/neuro/outputs/ \ participant \ - --action generalize + generalize ``` diff --git a/docs/source/usage_notes.rst b/docs/source/usage_notes.rst index a53987d..08f1696 100644 --- a/docs/source/usage_notes.rst +++ b/docs/source/usage_notes.rst @@ -22,5 +22,5 @@ Command-Line Arguments ---------------------- .. argparse:: :prog: bidsmreye - :module: bidsmreye.bidsmreye + :module: bidsmreye._parsers :func: common_parser diff --git a/pyproject.toml b/pyproject.toml index 361ed35..7ce7375 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,8 @@ dependencies = [ "rich", "tqdm", "tomli; python_version < '3.11'", - "keras<3.0.0" + "keras<3.0.0", + "rich_argparse" ] description = "bids app using deepMReye to decode eye motion for fMRI time series data" dynamic = ["version"] @@ -79,8 +80,8 @@ test = [ tests = ["bidsmreye[test]"] [project.scripts] -bidsmreye = "bidsmreye.bidsmreye:cli" -bidsmreye_model = "bidsmreye.download:cli" +bidsmreye = "bidsmreye._cli:cli" +bidsmreye_model = "bidsmreye._cli:cli_download" [project.urls] "Bug Tracker" = "https://github.com/cpp-lln-lab/bidsMReye/issues" @@ -112,7 +113,8 @@ root_package = "bidsmreye" [[tool.importlinter.contracts]] containers = "bidsmreye" layers = [ - "bidsmreye", + "_cli", + "_parsers | bidsmreye", "prepare_data | generalize | download", "quality_control", "visualize", @@ -126,11 +128,18 @@ layers = [ name = "Layered architecture" type = "layers" +[tool.isort] +combine_as_imports = true +line_length = 90 +profile = "black" +skip_gitignore = true + [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true +# enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] exclude = ['tests/'] no_implicit_optional = true plugins = "pydantic.mypy" @@ -156,7 +165,8 @@ module = [ 'plotly.*', 'pooch.*', 'rich.*', - 'scipy.*' + 'scipy.*', + "rich_argparse" ] [tool.pydantic-mypy] @@ -166,15 +176,10 @@ warn_required_dynamic_aliases = true warn_untyped_fields = true [tool.pytest.ini_options] -addopts = "--cov bidsmreye -ra --strict-config --strict-markers --doctest-modules --showlocals -s -vv --durations=0" +addopts = "-ra --cov bidsmreye --strict-config --strict-markers --doctest-modules --showlocals -s -vv --durations=0" doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS" junit_family = "xunit2" log_cli_level = "INFO" minversion = "6.0" testpaths = ["tests"] xfail_strict = true - -[tool.vulture] -min_confidence = 70 -paths = ["bidsmreye"] -sort_by_size = true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d656165 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,573 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --output-file=requirements.txt --strip-extras pyproject.toml +# +absl-py==2.1.0 + # via + # tensorboard + # tensorflow +antspyx==0.4.2 + # via deepmreye +anyio==4.4.0 + # via + # httpx + # jupyter-server +argon2-cffi==23.1.0 + # via jupyter-server +argon2-cffi-bindings==21.2.0 + # via argon2-cffi +arrow==1.3.0 + # via isoduration +astor==0.8.1 + # via formulaic +asttokens==2.4.1 + # via stack-data +astunparse==1.6.3 + # via tensorflow +async-lru==2.0.4 + # via jupyterlab +attrs==23.2.0 + # via + # bidsmreye (pyproject.toml) + # jsonschema + # referencing +babel==2.15.0 + # via jupyterlab-server +beautifulsoup4==4.12.3 + # via nbconvert +bids-validator==1.14.6 + # via pybids +bleach==6.1.0 + # via nbconvert +cachetools==5.4.0 + # via google-auth +certifi==2024.7.4 + # via + # httpcore + # httpx + # requests +cffi==1.16.0 + # via argon2-cffi-bindings +charset-normalizer==3.3.2 + # via requests +chart-studio==1.1.0 + # via antspyx +chevron==0.14.0 + # via bidsmreye (pyproject.toml) +click==8.1.7 + # via pybids +comm==0.2.2 + # via + # ipykernel + # ipywidgets +contourpy==1.2.1 + # via matplotlib +cycler==0.12.1 + # via matplotlib +debugpy==1.8.2 + # via ipykernel +decorator==5.1.1 + # via ipython +deepmreye==0.2.1 + # via bidsmreye (pyproject.toml) +defusedxml==0.7.1 + # via nbconvert +docopt==0.6.2 + # via num2words +executing==2.0.1 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +flatbuffers==24.3.25 + # via tensorflow +fonttools==4.53.1 + # via matplotlib +formulaic==0.5.2 + # via pybids +fqdn==1.5.1 + # via jsonschema +fsspec==2024.6.1 + # via universal-pathlib +gast==0.6.0 + # via tensorflow +google-auth==2.32.0 + # via + # google-auth-oauthlib + # tensorboard +google-auth-oauthlib==1.2.1 + # via tensorboard +google-pasta==0.2.0 + # via tensorflow +greenlet==3.0.3 + # via sqlalchemy +grpcio==1.65.2 + # via + # tensorboard + # tensorflow +h11==0.14.0 + # via httpcore +h5py==3.11.0 + # via tensorflow +httpcore==1.0.5 + # via httpx +httpx==0.27.0 + # via jupyterlab +idna==3.7 + # via + # anyio + # httpx + # jsonschema + # requests +imageio==2.34.2 + # via scikit-image +interface-meta==1.3.0 + # via formulaic +ipykernel==6.29.5 + # via + # jupyter + # jupyter-console + # jupyterlab + # qtconsole +ipython==8.26.0 + # via + # ipykernel + # ipywidgets + # jupyter-console +ipywidgets==8.1.3 + # via jupyter +isoduration==20.11.0 + # via jsonschema +jedi==0.19.1 + # via ipython +jinja2==3.1.4 + # via + # jupyter-server + # jupyterlab + # jupyterlab-server + # nbconvert +joblib==1.4.2 + # via scikit-learn +json5==0.9.25 + # via jupyterlab-server +jsonpointer==3.0.0 + # via jsonschema +jsonschema==4.23.0 + # via + # jupyter-events + # jupyterlab-server + # nbformat +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter==1.0.0 + # via deepmreye +jupyter-client==8.6.2 + # via + # ipykernel + # jupyter-console + # jupyter-server + # nbclient + # qtconsole +jupyter-console==6.6.3 + # via jupyter +jupyter-core==5.7.2 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server + # jupyterlab + # nbclient + # nbconvert + # nbformat + # qtconsole +jupyter-events==0.10.0 + # via jupyter-server +jupyter-lsp==2.2.5 + # via jupyterlab +jupyter-server==2.14.2 + # via + # jupyter-lsp + # jupyterlab + # jupyterlab-server + # notebook + # notebook-shim +jupyter-server-terminals==0.5.3 + # via jupyter-server +jupyterlab==4.2.4 + # via notebook +jupyterlab-pygments==0.3.0 + # via nbconvert +jupyterlab-server==2.27.3 + # via + # jupyterlab + # notebook +jupyterlab-widgets==3.0.11 + # via ipywidgets +kaleido==0.2.1 + # via bidsmreye (pyproject.toml) +keras==2.15.0 + # via + # bidsmreye (pyproject.toml) + # tensorflow +kiwisolver==1.4.5 + # via matplotlib +lazy-loader==0.4 + # via scikit-image +libclang==18.1.1 + # via tensorflow +markdown==3.6 + # via tensorboard +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via + # jinja2 + # nbconvert + # werkzeug +matplotlib==3.9.1 + # via + # antspyx + # deepmreye +matplotlib-inline==0.1.7 + # via + # ipykernel + # ipython +mdurl==0.1.2 + # via markdown-it-py +mistune==3.0.2 + # via nbconvert +ml-dtypes==0.3.2 + # via tensorflow +nbclient==0.10.0 + # via nbconvert +nbconvert==7.16.4 + # via + # jupyter + # jupyter-server +nbformat==5.10.4 + # via + # jupyter-server + # nbclient + # nbconvert +nest-asyncio==1.6.0 + # via ipykernel +networkx==3.3 + # via scikit-image +nibabel==5.2.1 + # via + # antspyx + # pybids +notebook==7.2.1 + # via jupyter +notebook-shim==0.2.4 + # via + # jupyterlab + # notebook +num2words==0.5.13 + # via pybids +numpy==1.26.4 + # via + # antspyx + # contourpy + # deepmreye + # formulaic + # h5py + # imageio + # matplotlib + # ml-dtypes + # nibabel + # opt-einsum + # pandas + # patsy + # pybids + # scikit-image + # scikit-learn + # scipy + # statsmodels + # tensorboard + # tensorflow + # tifffile +oauthlib==3.2.2 + # via requests-oauthlib +opt-einsum==3.3.0 + # via tensorflow +overrides==7.7.0 + # via jupyter-server +packaging==24.1 + # via + # ipykernel + # jupyter-server + # jupyterlab + # jupyterlab-server + # lazy-loader + # matplotlib + # nbconvert + # nibabel + # plotly + # pooch + # qtconsole + # qtpy + # scikit-image + # statsmodels + # tensorflow +pandas==2.2.2 + # via + # antspyx + # deepmreye + # formulaic + # pybids + # statsmodels +pandocfilters==1.5.1 + # via nbconvert +parso==0.8.4 + # via jedi +patsy==0.5.6 + # via statsmodels +pexpect==4.9.0 + # via ipython +pillow==10.4.0 + # via + # antspyx + # imageio + # matplotlib + # scikit-image +platformdirs==4.2.2 + # via + # jupyter-core + # pooch +plotly==5.23.0 + # via + # chart-studio + # deepmreye +pooch==1.8.2 + # via bidsmreye (pyproject.toml) +prometheus-client==0.20.0 + # via jupyter-server +prompt-toolkit==3.0.47 + # via + # ipython + # jupyter-console +protobuf==4.25.4 + # via + # tensorboard + # tensorflow +psutil==6.0.0 + # via ipykernel +ptyprocess==0.7.0 + # via + # pexpect + # terminado +pure-eval==0.2.3 + # via stack-data +pyasn1==0.6.0 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.4.0 + # via google-auth +pybids==0.17.0 + # via bidsmreye (pyproject.toml) +pycparser==2.22 + # via cffi +pygments==2.18.0 + # via + # ipython + # jupyter-console + # nbconvert + # qtconsole + # rich +pyparsing==3.1.2 + # via matplotlib +python-dateutil==2.9.0.post0 + # via + # arrow + # jupyter-client + # matplotlib + # pandas +python-json-logger==2.0.7 + # via jupyter-events +pytz==2024.1 + # via pandas +pyyaml==6.0.1 + # via + # antspyx + # jupyter-events +pyzmq==26.0.3 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server + # qtconsole +qtconsole==5.5.2 + # via jupyter +qtpy==2.4.1 + # via qtconsole +referencing==0.35.1 + # via + # jsonschema + # jsonschema-specifications + # jupyter-events +requests==2.32.3 + # via + # chart-studio + # jupyterlab-server + # pooch + # requests-oauthlib + # tensorboard +requests-oauthlib==2.0.0 + # via google-auth-oauthlib +retrying==1.3.4 + # via chart-studio +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rich==13.7.1 + # via bidsmreye (pyproject.toml) +rpds-py==0.19.1 + # via + # jsonschema + # referencing +rsa==4.9 + # via google-auth +scikit-image==0.24.0 + # via antspyx +scikit-learn==1.5.1 + # via + # antspyx + # deepmreye +scipy==1.14.0 + # via + # antspyx + # deepmreye + # formulaic + # pybids + # scikit-image + # scikit-learn + # statsmodels +send2trash==1.8.3 + # via jupyter-server +six==1.16.0 + # via + # asttokens + # astunparse + # bleach + # chart-studio + # google-pasta + # patsy + # python-dateutil + # retrying + # rfc3339-validator + # tensorboard + # tensorflow +sniffio==1.3.1 + # via + # anyio + # httpx +soupsieve==2.5 + # via beautifulsoup4 +sqlalchemy==2.0.31 + # via pybids +stack-data==0.6.3 + # via ipython +statsmodels==0.14.2 + # via antspyx +tenacity==9.0.0 + # via plotly +tensorboard==2.15.2 + # via tensorflow +tensorboard-data-server==0.7.2 + # via tensorboard +tensorflow==2.15.1 + # via deepmreye +tensorflow-estimator==2.15.0 + # via tensorflow +tensorflow-io-gcs-filesystem==0.37.1 + # via tensorflow +termcolor==2.4.0 + # via tensorflow +terminado==0.18.1 + # via + # jupyter-server + # jupyter-server-terminals +threadpoolctl==3.5.0 + # via scikit-learn +tifffile==2024.7.24 + # via scikit-image +tinycss2==1.3.0 + # via nbconvert +tornado==6.4.1 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # notebook + # terminado +tqdm==4.66.4 + # via bidsmreye (pyproject.toml) +traitlets==5.14.3 + # via + # comm + # ipykernel + # ipython + # ipywidgets + # jupyter-client + # jupyter-console + # jupyter-core + # jupyter-events + # jupyter-server + # jupyterlab + # matplotlib-inline + # nbclient + # nbconvert + # nbformat + # qtconsole +types-python-dateutil==2.9.0.20240316 + # via arrow +typing-extensions==4.12.2 + # via + # formulaic + # ipython + # sqlalchemy + # tensorflow +tzdata==2024.1 + # via pandas +universal-pathlib==0.2.2 + # via pybids +uri-template==1.3.0 + # via jsonschema +urllib3==2.2.2 + # via requests +wcwidth==0.2.13 + # via prompt-toolkit +webcolors==24.6.0 + # via + # antspyx + # jsonschema +webencodings==0.5.1 + # via + # bleach + # tinycss2 +websocket-client==1.8.0 + # via jupyter-server +werkzeug==3.0.3 + # via tensorboard +wheel==0.43.0 + # via astunparse +widgetsnbextension==4.0.11 + # via ipywidgets +wrapt==1.14.1 + # via + # formulaic + # tensorflow + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..d420712 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests.""" diff --git a/tests/data/.gitignore b/tests/data/.gitignore index fd9d048..e001a9e 100644 --- a/tests/data/.gitignore +++ b/tests/data/.gitignore @@ -1,3 +1,4 @@ moae_fmriprep ds000201-der/derivatives +ds000114-fmriprep/ ds002799 diff --git a/tests/test_download.py b/tests/test_download.py index 574efa7..22d25f6 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -5,24 +5,7 @@ import pytest -from bidsmreye.download import download, download_parser - - -def test_download_parser(): - parser = download_parser() - - assert parser.description == "Download deepmreye pretrained model from OSF." - - args, _ = parser.parse_known_args( - [ - "--model_name", - "1_guided_fixations", - "--output_dir", - "/home/bob/models", - ] - ) - - assert args.output_dir == "/home/bob/models" +from bidsmreye.download import download def test_download(tmp_path): diff --git a/tests/test_methods.py b/tests/test_methods.py index 9cb4069..2bd91ad 100644 --- a/tests/test_methods.py +++ b/tests/test_methods.py @@ -1,8 +1,5 @@ from __future__ import annotations -import shutil -from pathlib import Path - from bidsmreye.methods import methods diff --git a/tests/test_bidsmreye.py b/tests/test_parsers.py similarity index 65% rename from tests/test_bidsmreye.py rename to tests/test_parsers.py index feb18e5..5677941 100644 --- a/tests/test_bidsmreye.py +++ b/tests/test_parsers.py @@ -1,6 +1,6 @@ from __future__ import annotations -from bidsmreye.bidsmreye import common_parser +from bidsmreye._parsers import common_parser, download_parser def test_parser() -> None: @@ -10,7 +10,6 @@ def test_parser() -> None: "/path/to/bids", "/path/to/output", "participant", - "--action", "prepare", "--task", "foo", @@ -36,7 +35,6 @@ def test_parser_basic() -> None: "/path/to/bids", "/path/to/output", "participant", - "--action", "prepare", "--task", "foo", @@ -45,4 +43,21 @@ def test_parser_basic() -> None: ) assert args.task == ["foo", "bar"] - assert args.non_linear_coreg == False + assert args.non_linear_coreg is False + + +def test_download_parser(): + parser = download_parser() + + assert parser.description == "Download deepmreye pretrained model from OSF." + + args, _ = parser.parse_known_args( + [ + "--model_name", + "1_guided_fixations", + "--output_dir", + "/home/bob/models", + ] + ) + + assert args.output_dir == "/home/bob/models" diff --git a/tests/test_utils.py b/tests/test_utils.py index bfc357a..985dc2d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,5 @@ from __future__ import annotations -import shutil from pathlib import Path from bidsmreye.bids_utils import get_dataset_layout @@ -34,7 +33,10 @@ def test_get_deepmreye_filename(): "sub-01", "ses-01", "func", - "mask_sub-01_ses-01_task-nback_run-01_space-MNI152NLin2009cAsym_desc-preproc_bold.p", + ( + "mask_sub-01_ses-01_task-nback_run-01_" + "space-MNI152NLin2009cAsym_desc-preproc_bold.p" + ), ) img = layout.get( diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..48c5799 --- /dev/null +++ b/tox.ini @@ -0,0 +1,36 @@ +; See https://tox.wiki/en +[tox] +requires = + tox>=4 +; run lint by default when just calling "tox" +env_list = lint + +; ENVIRONMENTS +; ------------ +[style] +description = common environment for style checkers (rely on pre-commit hooks) +skip_install = true +deps = + pre-commit + import-linter + +; COMMANDS +; -------- +[testenv:lint] +description = install pre-commit hooks and run all linters and formatters +skip_install = true +deps = + {[style]deps} +commands = + pre-commit install + pre-commit run --all-files --show-diff-on-failure {posargs:} + lint-imports --config pyproject.toml + + +[testenv:update_dependencies] +description = update requirements.txt +skip_install = true +deps = + pip-tools +commands = + pip-compile --strip-extras -o requirements.txt pyproject.toml{posargs:}