From f84af43b1aec0abde27f61d4a0e6edd1750b7e50 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sun, 5 May 2024 14:00:16 -0400 Subject: [PATCH 01/15] Add maintenance scripts. --- .maint/ci/activate.sh | 9 ++++++++ .maint/ci/build_archive.sh | 34 +++++++++++++++++++++++++++++++ .maint/ci/check.sh | 26 +++++++++++++++++++++++ .maint/ci/create_venv.sh | 24 ++++++++++++++++++++++ .maint/ci/env.sh | 6 ++++++ .maint/ci/install.sh | 34 +++++++++++++++++++++++++++++++ .maint/ci/install_dependencies.sh | 21 +++++++++++++++++++ .maint/ci/install_extras.sh | 24 ++++++++++++++++++++++ 8 files changed, 178 insertions(+) create mode 100644 .maint/ci/activate.sh create mode 100755 .maint/ci/build_archive.sh create mode 100755 .maint/ci/check.sh create mode 100755 .maint/ci/create_venv.sh create mode 100644 .maint/ci/env.sh create mode 100755 .maint/ci/install.sh create mode 100755 .maint/ci/install_dependencies.sh create mode 100755 .maint/ci/install_extras.sh diff --git a/.maint/ci/activate.sh b/.maint/ci/activate.sh new file mode 100644 index 0000000..567e13a --- /dev/null +++ b/.maint/ci/activate.sh @@ -0,0 +1,9 @@ +if [ -e virtenv/bin/activate ]; then + source virtenv/bin/activate +elif [ -e virtenv/Scripts/activate ]; then + source virtenv/Scripts/activate +else + echo Cannot activate virtual environment + ls -R virtenv + false +fi diff --git a/.maint/ci/build_archive.sh b/.maint/ci/build_archive.sh new file mode 100755 index 0000000..6e8be6f --- /dev/null +++ b/.maint/ci/build_archive.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +echo "Building archive" + +source .maint/ci/activate.sh + +set -eu + +# Required dependencies +echo "INSTALL_TYPE = $INSTALL_TYPE" + +set -x + +if [ "$INSTALL_TYPE" = "sdist" -o "$INSTALL_TYPE" = "wheel" ]; then + python -m build +elif [ "$INSTALL_TYPE" = "archive" ]; then + ARCHIVE="/tmp/package.tar.gz" + git archive -o $ARCHIVE HEAD +fi + +if [ "$INSTALL_TYPE" = "sdist" ]; then + ARCHIVE=$( ls $PWD/dist/*.tar.gz ) +elif [ "$INSTALL_TYPE" = "wheel" ]; then + ARCHIVE=$( ls $PWD/dist/*.whl ) +elif [ "$INSTALL_TYPE" = "pip" ]; then + ARCHIVE="$PWD" +fi + +if [ "$INSTALL_TYPE" = "sdist" -o "$INSTALL_TYPE" = "wheel" ]; then + python -m pip install twine + python -m twine check $ARCHIVE +fi + +set +eux diff --git a/.maint/ci/check.sh b/.maint/ci/check.sh new file mode 100755 index 0000000..7b562ba --- /dev/null +++ b/.maint/ci/check.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +echo Running tests + +source .maint/ci/activate.sh + +set -eu + +# Required variables +echo CHECK_TYPE = $CHECK_TYPE + +set -x + +if [ "${CHECK_TYPE}" == "doc" ]; then + cd doc + make html && make doctest +elif [ "${CHECK_TYPE}" == "tests" ]; then + pytest --doctest-modules --cov fmripost_aroma --cov-report xml \ + --junitxml=test-results.xml -v fmripost_aroma +else + false +fi + +set +eux + +echo Done running tests diff --git a/.maint/ci/create_venv.sh b/.maint/ci/create_venv.sh new file mode 100755 index 0000000..6b6822e --- /dev/null +++ b/.maint/ci/create_venv.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo Creating isolated virtual environment + +source .maint/ci/env.sh + +set -eu + +# Required variables +echo SETUP_REQUIRES = $SETUP_REQUIRES + +set -x + +python -m pip install --upgrade pip virtualenv +virtualenv --python=python virtenv +source .maint/ci/activate.sh +python --version +python -m pip install -U $SETUP_REQUIRES +which python +which pip + +set +eux + +echo Done creating isolated virtual environment diff --git a/.maint/ci/env.sh b/.maint/ci/env.sh new file mode 100644 index 0000000..65e148b --- /dev/null +++ b/.maint/ci/env.sh @@ -0,0 +1,6 @@ +SETUP_REQUIRES="pip build" + +# Numpy and scipy upload nightly/weekly/intermittent wheels +NIGHTLY_WHEELS="https://pypi.anaconda.org/scipy-wheels-nightly/simple" +STAGING_WHEELS="https://pypi.anaconda.org/multibuild-wheels-staging/simple" +PRE_PIP_FLAGS="--pre --extra-index-url $NIGHTLY_WHEELS --extra-index-url $STAGING_WHEELS --prefer-binary" diff --git a/.maint/ci/install.sh b/.maint/ci/install.sh new file mode 100755 index 0000000..b9faf63 --- /dev/null +++ b/.maint/ci/install.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +echo Installing fmripost_aroma + +source .maint/ci/activate.sh +source .maint/ci/env.sh + +set -eu + +# Required variables +echo INSTALL_TYPE = $INSTALL_TYPE +echo CHECK_TYPE = $CHECK_TYPE +echo EXTRA_PIP_FLAGS = $EXTRA_PIP_FLAGS + +set -x + +if [ -n "$EXTRA_PIP_FLAGS" ]; then + EXTRA_PIP_FLAGS=${!EXTRA_PIP_FLAGS} +fi + +pip install $EXTRA_PIP_FLAGS $ARCHIVE + +# Basic import check +python -c 'import fmripost_aroma; print(fmripost_aroma.__version__)' + +if [ "$CHECK_TYPE" == "skiptests" ]; then + exit 0 +fi + +pip install $EXTRA_PIP_FLAGS "fmripost_aroma[$CHECK_TYPE]" + +set +eux + +echo Done installing fmripost_aroma diff --git a/.maint/ci/install_dependencies.sh b/.maint/ci/install_dependencies.sh new file mode 100755 index 0000000..7e0e33f --- /dev/null +++ b/.maint/ci/install_dependencies.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +echo "Installing dependencies" + +set -eu + +# Required variables +echo OS_TYPE = $OS_TYPE + +if [ "$OS_TYPE" = "ubuntu-latest" ]; then + sudo apt update + sudo apt install -y graphviz +elif [ "$OS_TYPE" = "macos-latest" ]; then + brew install graphviz +else + echo "Unknown OS_TYPE: $OS_TYPE" +fi + +set +eux + +echo Done installing dependencies diff --git a/.maint/ci/install_extras.sh b/.maint/ci/install_extras.sh new file mode 100755 index 0000000..19d4449 --- /dev/null +++ b/.maint/ci/install_extras.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo Installing dependencies + +source .maint/ci/activate.sh +source .maint/ci/env.sh + +set -eu + +# Required variables +echo EXTRA_PIP_FLAGS = $EXTRA_PIP_FLAGS +echo CHECK_TYPE = $CHECK_TYPE + +set -x + +if [ -n "$EXTRA_PIP_FLAGS" ]; then + EXTRA_PIP_FLAGS=${!EXTRA_PIP_FLAGS} +fi + +pip install $EXTRA_PIP_FLAGS "fmripost_aroma[$CHECK_TYPE]" + +set +eux + +echo Done installing dependencies From 2e4d4e4eedfcff3d362a4163f7861c5164b2adeb Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sun, 5 May 2024 14:08:57 -0400 Subject: [PATCH 02/15] Run black. --- docs/conf.py | 120 +++++----- docs/sphinxext/github_link.py | 18 +- scripts/fetch_templates.py | 56 ++--- src/fmripost_aroma/__init__.py | 2 +- src/fmripost_aroma/cli/parser.py | 24 +- src/fmripost_aroma/cli/run.py | 17 +- src/fmripost_aroma/data/__init__.py | 1 + src/fmripost_aroma/utils/bids.py | 3 +- src/fmripost_aroma/workflows/__init__.py | 2 +- .../workflows/tests/__init__.py | 12 +- .../workflows/tests/test_base.py | 210 +++++++++--------- 11 files changed, 237 insertions(+), 228 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d81e650..90ac86c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,47 +19,47 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('sphinxext')) -sys.path.insert(0, os.path.abspath('../wrapper')) +sys.path.append(os.path.abspath("sphinxext")) +sys.path.insert(0, os.path.abspath("../wrapper")) from github_link import make_linkcode_resolve # noqa: E402 # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.5.3' +needs_sphinx = "1.5.3" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.linkcode', - 'sphinx.ext.napoleon', - 'sphinxarg.ext', # argparse extension - 'nipype.sphinxext.plot_workflow', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.linkcode", + "sphinx.ext.napoleon", + "sphinxarg.ext", # argparse extension + "nipype.sphinxext.plot_workflow", ] # Mock modules in autodoc: autodoc_mock_imports = [ - 'numpy', - 'nitime', - 'matplotlib', + "numpy", + "nitime", + "matplotlib", ] -if pver.parse(sphinxversion) >= pver.parse('1.7.0'): +if pver.parse(sphinxversion) >= pver.parse("1.7.0"): autodoc_mock_imports += [ - 'pandas', - 'nilearn', - 'seaborn', + "pandas", + "nilearn", + "seaborn", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # Accept custom section names to be parsed for numpy-style docstrings # of parameters. @@ -67,41 +67,41 @@ # https://github.com/sphinx-contrib/napoleon/pull/10 is merged. napoleon_use_param = False napoleon_custom_sections = [ - ('Inputs', 'Parameters'), - ('Outputs', 'Parameters'), + ("Inputs", "Parameters"), + ("Outputs", "Parameters"), ] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = [".rst", ".md"] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = "utf-8-sig" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'fMRIPost-AROMA' -author = 'The fMRIPost-AROMA developers' -copyright = f'2016-, {author}' +project = "fMRIPost-AROMA" +author = "The fMRIPost-AROMA developers" +copyright = f"2016-, {author}" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = 'version' +version = "version" # The full version, including alpha/beta/rc tags. -release = 'version' +release = "version" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -112,7 +112,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -130,7 +130,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -146,7 +146,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -175,7 +175,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -240,7 +240,7 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'fmripost_aroma_doc' +htmlhelp_basename = "fmripost_aroma_doc" # -- Options for LaTeX output --------------------------------------------- @@ -259,7 +259,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'fmripost_aroma.tex', 'fMRIPost-AROMA Documentation', author, 'manual'), + (master_doc, "fmripost_aroma.tex", "fMRIPost-AROMA Documentation", author, "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -287,7 +287,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, 'fmripost-aroma', 'fMRIPost-AROMA Documentation', [author], 1)] +man_pages = [(master_doc, "fmripost-aroma", "fMRIPost-AROMA Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -301,12 +301,12 @@ texinfo_documents = [ ( master_doc, - 'fMRIPost-AROMA', - 'fMRIPost-AROMA Documentation', + "fMRIPost-AROMA", + "fMRIPost-AROMA Documentation", author, - 'fMRIPost-AROMA', - 'One line description of project.', - 'Miscellaneous', + "fMRIPost-AROMA", + "One line description of project.", + "Miscellaneous", ), ] @@ -324,31 +324,31 @@ # The following is used by sphinx.ext.linkcode to provide links to github linkcode_resolve = make_linkcode_resolve( - 'fmripost_aroma', - 'https://github.com/nipreps/fMRIPost-AROMA/blob/{revision}/{package}/{path}#L{lineno}', + "fmripost_aroma", + "https://github.com/nipreps/fMRIPost-AROMA/blob/{revision}/{package}/{path}#L{lineno}", ) # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'numpy': ('https://numpy.org/doc/stable/', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/', None), - 'matplotlib': ('https://matplotlib.org/stable/', None), - 'bids': ('https://bids-standard.github.io/pybids/', None), - 'nibabel': ('https://nipy.org/nibabel/', None), - 'nipype': ('https://nipype.readthedocs.io/en/latest/', None), - 'niworkflows': ('https://www.nipreps.org/niworkflows/', None), - 'fmriprep': ('https://fmriprep.org/en/stable/', None), - 'sdcflows': ('https://www.nipreps.org/sdcflows/', None), - 'smriprep': ('https://www.nipreps.org/smriprep/', None), - 'templateflow': ('https://www.templateflow.org/python-client', None), - 'tedana': ('https://tedana.readthedocs.io/en/latest/', None), + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "bids": ("https://bids-standard.github.io/pybids/", None), + "nibabel": ("https://nipy.org/nibabel/", None), + "nipype": ("https://nipype.readthedocs.io/en/latest/", None), + "niworkflows": ("https://www.nipreps.org/niworkflows/", None), + "fmriprep": ("https://fmriprep.org/en/stable/", None), + "sdcflows": ("https://www.nipreps.org/sdcflows/", None), + "smriprep": ("https://www.nipreps.org/smriprep/", None), + "templateflow": ("https://www.templateflow.org/python-client", None), + "tedana": ("https://tedana.readthedocs.io/en/latest/", None), } -suppress_warnings = ['image.nonlocal_uri'] +suppress_warnings = ["image.nonlocal_uri"] def setup(app): - app.add_css_file('theme_overrides.css') + app.add_css_file("theme_overrides.css") # We need this for the boilerplate script - app.add_js_file('https://cdn.rawgit.com/chrisfilo/zenodo.js/v0.1/zenodo.js') + app.add_js_file("https://cdn.rawgit.com/chrisfilo/zenodo.js/v0.1/zenodo.js") diff --git a/docs/sphinxext/github_link.py b/docs/sphinxext/github_link.py index fabba0a..5dc699a 100644 --- a/docs/sphinxext/github_link.py +++ b/docs/sphinxext/github_link.py @@ -10,16 +10,16 @@ from functools import partial from operator import attrgetter -REVISION_CMD = 'git rev-parse --short HEAD' +REVISION_CMD = "git rev-parse --short HEAD" def _get_git_revision(): try: revision = subprocess.check_output(REVISION_CMD.split()).strip() except (subprocess.CalledProcessError, OSError): - print('Failed to execute git to get revision') + print("Failed to execute git to get revision") return None - return revision.decode('utf-8') + return revision.decode("utf-8") def _linkcode_resolve(domain, info, package, url_fmt, revision): @@ -39,14 +39,14 @@ def _linkcode_resolve(domain, info, package, url_fmt, revision): if revision is None: return - if domain not in ('py', 'pyx'): + if domain not in ("py", "pyx"): return - if not info.get('module') or not info.get('fullname'): + if not info.get("module") or not info.get("fullname"): return - class_name = info['fullname'].split('.')[0] - module = __import__(info['module'], fromlist=[class_name]) - obj = attrgetter(info['fullname'])(module) + class_name = info["fullname"].split(".")[0] + module = __import__(info["module"], fromlist=[class_name]) + obj = attrgetter(info["fullname"])(module) # Unwrap the object to get the correct source # file in case that is wrapped by a decorator @@ -68,7 +68,7 @@ def _linkcode_resolve(domain, info, package, url_fmt, revision): try: lineno = inspect.getsourcelines(obj)[1] except Exception: - lineno = '' + lineno = "" return url_fmt.format(revision=revision, package=package, path=fn, lineno=lineno) diff --git a/scripts/fetch_templates.py b/scripts/fetch_templates.py index 04881d6..11b1ac0 100755 --- a/scripts/fetch_templates.py +++ b/scripts/fetch_templates.py @@ -23,13 +23,13 @@ def fetch_MNI2009(): tpl-MNI152NLin2009cAsym/tpl-MNI152NLin2009cAsym_res-02_desc-fMRIPrep_boldref.nii.gz tpl-MNI152NLin2009cAsym/tpl-MNI152NLin2009cAsym_res-01_label-brain_probseg.nii.gz """ - template = 'MNI152NLin2009cAsym' + template = "MNI152NLin2009cAsym" - tf.get(template, resolution=(1, 2), desc=None, suffix='T1w') - tf.get(template, resolution=(1, 2), desc='brain', suffix='mask') - tf.get(template, resolution=1, atlas=None, desc='carpet', suffix='dseg') - tf.get(template, resolution=2, desc='fMRIPrep', suffix='boldref') - tf.get(template, resolution=1, label='brain', suffix='probseg') + tf.get(template, resolution=(1, 2), desc=None, suffix="T1w") + tf.get(template, resolution=(1, 2), desc="brain", suffix="mask") + tf.get(template, resolution=1, atlas=None, desc="carpet", suffix="dseg") + tf.get(template, resolution=2, desc="fMRIPrep", suffix="boldref") + tf.get(template, resolution=1, label="brain", suffix="probseg") def fetch_MNI6(): @@ -42,12 +42,12 @@ def fetch_MNI6(): tpl-MNI152NLin6Asym/tpl-MNI152NLin6Asym_res-02_desc-brain_mask.nii.gz tpl-MNI152NLin6Asym/tpl-MNI152NLin6Asym_res-02_atlas-HCP_dseg.nii.gz """ - template = 'MNI152NLin6Asym' + template = "MNI152NLin6Asym" - tf.get(template, resolution=(1, 2), desc=None, suffix='T1w') - tf.get(template, resolution=(1, 2), desc='brain', suffix='mask') + tf.get(template, resolution=(1, 2), desc=None, suffix="T1w") + tf.get(template, resolution=(1, 2), desc="brain", suffix="mask") # CIFTI - tf.get(template, resolution=2, atlas='HCP', suffix='dseg') + tf.get(template, resolution=2, atlas="HCP", suffix="dseg") def fetch_OASIS(): @@ -61,14 +61,14 @@ def fetch_OASIS(): tpl-OASIS30ANTs/tpl-OASIS30ANTs_res-01_desc-brain_mask.nii.gz tpl-OASIS30ANTs/tpl-OASIS30ANTs_res-01_desc-BrainCerebellumExtraction_mask.nii.gz """ - template = 'OASIS30ANTs' + template = "OASIS30ANTs" - tf.get(template, resolution=1, desc=None, label=None, suffix='T1w') - tf.get(template, resolution=1, label='WM', suffix='probseg') - tf.get(template, resolution=1, label='BS', suffix='probseg') - tf.get(template, resolution=1, label='brain', suffix='probseg') - tf.get(template, resolution=1, label='brain', suffix='mask') - tf.get(template, resolution=1, desc='BrainCerebellumExtraction', suffix='mask') + tf.get(template, resolution=1, desc=None, label=None, suffix="T1w") + tf.get(template, resolution=1, label="WM", suffix="probseg") + tf.get(template, resolution=1, label="BS", suffix="probseg") + tf.get(template, resolution=1, label="brain", suffix="probseg") + tf.get(template, resolution=1, label="brain", suffix="mask") + tf.get(template, resolution=1, desc="BrainCerebellumExtraction", suffix="mask") def fetch_fsaverage(): @@ -84,11 +84,11 @@ def fetch_fsaverage(): tpl-fsaverage/tpl-fsaverage_hemi-L_den-164k_sulc.shape.gii tpl-fsaverage/tpl-sfaverage_hemi-R_den-164k_sulc.shape.gii """ - template = 'fsaverage' + template = "fsaverage" - tf.get(template, density='164k', desc='std', suffix='sphere') - tf.get(template, density='164k', suffix='midthickness') - tf.get(template, density='164k', suffix='sulc') + tf.get(template, density="164k", desc="std", suffix="sphere") + tf.get(template, density="164k", suffix="midthickness") + tf.get(template, density="164k", suffix="sulc") def fetch_fsLR(): @@ -104,7 +104,7 @@ def fetch_fsLR(): tpl-fsLR/tpl-fsLR_space-fsaverage_hemi-L_den-32k_sphere.surf.gii tpl-fsLR/tpl-fsLR_space-fsaverage_hemi-R_den-32k_sphere.surf.gii """ - tf.get('fsLR', density='32k') + tf.get("fsLR", density="32k") def fetch_all(): @@ -115,21 +115,21 @@ def fetch_all(): # fetch_fsLR() -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser( - description='Helper script for pre-caching required templates to run fMRIPost-AROMA', + description="Helper script for pre-caching required templates to run fMRIPost-AROMA", ) parser.add_argument( - '--tf-dir', + "--tf-dir", type=os.path.abspath, - help='Directory to save templates in. If not provided, templates will be saved to' - ' `${HOME}/.cache/templateflow`.', + help="Directory to save templates in. If not provided, templates will be saved to" + " `${HOME}/.cache/templateflow`.", ) opts = parser.parse_args() # set envvar (if necessary) prior to templateflow import if opts.tf_dir is not None: - os.environ['TEMPLATEFLOW_HOME'] = opts.tf_dir + os.environ["TEMPLATEFLOW_HOME"] = opts.tf_dir import templateflow.api as tf diff --git a/src/fmripost_aroma/__init__.py b/src/fmripost_aroma/__init__.py index 2afc7fe..d34bde0 100644 --- a/src/fmripost_aroma/__init__.py +++ b/src/fmripost_aroma/__init__.py @@ -25,4 +25,4 @@ try: from ._version import __version__ except ImportError: - __version__ = '0+unknown' + __version__ = "0+unknown" diff --git a/src/fmripost_aroma/cli/parser.py b/src/fmripost_aroma/cli/parser.py index 0a53310..aa272c2 100644 --- a/src/fmripost_aroma/cli/parser.py +++ b/src/fmripost_aroma/cli/parser.py @@ -45,14 +45,14 @@ def __call__(self, parser, namespace, values, option_string=None): d = {} for spec in values: try: - name, loc = spec.split('=') + name, loc = spec.split("=") loc = Path(loc) except ValueError: loc = Path(spec) name = loc.name if name in d: - raise ValueError(f'Received duplicate derivative name: {name}') + raise ValueError(f"Received duplicate derivative name: {name}") d[name] = loc setattr(namespace, self.dest, d) @@ -189,16 +189,16 @@ def _bids_filter(value, parser): ), ) g_bids.add_argument( - '-d', - '--derivatives', + "-d", + "--derivatives", action=ToDict, - metavar='PACKAGE=PATH', + metavar="PACKAGE=PATH", type=str, - nargs='+', + nargs="+", help=( - 'Search PATH(s) for pre-computed derivatives. ' - 'These may be provided as named folders ' - '(e.g., `--derivatives smriprep=/path/to/smriprep`).' + "Search PATH(s) for pre-computed derivatives. " + "These may be provided as named folders " + "(e.g., `--derivatives smriprep=/path/to/smriprep`)." ), ) g_bids.add_argument( @@ -292,8 +292,8 @@ def _bids_filter(value, parser): ), ) g_conf.add_argument( - '--output-spaces', - nargs='*', + "--output-spaces", + nargs="*", action=OutputReferencesAction, help="""\ Standard and non-standard spaces to resample denoised functional images to. \ @@ -304,7 +304,7 @@ def _bids_filter(value, parser): Non-standard spaces imply specific orientations and sampling grids. \ For further details, please check out \ https://fmriprep.readthedocs.io/en/%s/spaces.html""" - % (currentv.base_version if is_release else 'latest'), + % (currentv.base_version if is_release else "latest"), ) g_conf.add_argument( "--dummy-scans", diff --git a/src/fmripost_aroma/cli/run.py b/src/fmripost_aroma/cli/run.py index 7c4112e..eb92659 100644 --- a/src/fmripost_aroma/cli/run.py +++ b/src/fmripost_aroma/cli/run.py @@ -140,7 +140,9 @@ def main(): config.loggers.workflow.log( 15, - "\n".join(["fMRIPost-AROMA config:"] + ["\t\t%s" % s for s in config.dumps().splitlines()]), + "\n".join( + ["fMRIPost-AROMA config:"] + ["\t\t%s" % s for s in config.dumps().splitlines()] + ), ) config.loggers.workflow.log(25, "fMRIPost-AROMA started!") errno = 1 # Default is error exit unless otherwise set @@ -151,7 +153,10 @@ def main(): from fmripost_aroma.utils.telemetry import process_crashfile crashfolders = [ - config.execution.fmripost_aroma_dir / f"sub-{s}" / "log" / config.execution.run_uuid + config.execution.fmripost_aroma_dir + / f"sub-{s}" + / "log" + / config.execution.run_uuid for s in config.execution.participant_label ] for crashfolder in crashfolders: @@ -195,7 +200,9 @@ def main(): dseg_tsv = str(api.get("fsaverage", suffix="dseg", extension=[".tsv"])) _copy_any(dseg_tsv, str(config.execution.fmripost_aroma_dir / "desc-aseg_dseg.tsv")) - _copy_any(dseg_tsv, str(config.execution.fmripost_aroma_dir / "desc-aparcaseg_dseg.tsv")) + _copy_any( + dseg_tsv, str(config.execution.fmripost_aroma_dir / "desc-aparcaseg_dseg.tsv") + ) errno = 0 finally: from pkg_resources import resource_filename as pkgrf @@ -217,7 +224,9 @@ def main(): config=pkgrf("fmripost_aroma", "data/reports-spec.yml"), packagename="fmripost_aroma", ) - write_derivative_description(config.execution.bids_dir, config.execution.fmripost_aroma_dir) + write_derivative_description( + config.execution.bids_dir, config.execution.fmripost_aroma_dir + ) write_bidsignore(config.execution.fmripost_aroma_dir) if sentry_sdk is not None and failed_reports: diff --git a/src/fmripost_aroma/data/__init__.py b/src/fmripost_aroma/data/__init__.py index 9fea898..7cb3508 100644 --- a/src/fmripost_aroma/data/__init__.py +++ b/src/fmripost_aroma/data/__init__.py @@ -10,6 +10,7 @@ .. autoclass:: Loader """ + from __future__ import annotations import atexit diff --git a/src/fmripost_aroma/utils/bids.py b/src/fmripost_aroma/utils/bids.py index d88a4a5..88f6ad4 100644 --- a/src/fmripost_aroma/utils/bids.py +++ b/src/fmripost_aroma/utils/bids.py @@ -76,8 +76,7 @@ def collect_derivatives_old( """Collect preprocessing derivatives.""" subj_data = { "bold_raw": "", - "" - "bold_boldref": "", + "" "bold_boldref": "", "bold_MNI152NLin6": "", } query = { diff --git a/src/fmripost_aroma/workflows/__init__.py b/src/fmripost_aroma/workflows/__init__.py index ab66ee9..658bfbb 100644 --- a/src/fmripost_aroma/workflows/__init__.py +++ b/src/fmripost_aroma/workflows/__init__.py @@ -2,4 +2,4 @@ from fmripost_aroma.workflows import aroma, base -__all__ = ["aroma", "base"] \ No newline at end of file +__all__ = ["aroma", "base"] diff --git a/src/fmripost_aroma/workflows/tests/__init__.py b/src/fmripost_aroma/workflows/tests/__init__.py index 74d2074..5f96efc 100644 --- a/src/fmripost_aroma/workflows/tests/__init__.py +++ b/src/fmripost_aroma/workflows/tests/__init__.py @@ -38,13 +38,13 @@ def mock_config(bids_dir=None): """Create a mock config for documentation and testing purposes.""" from ... import config - _old_fs = os.getenv('FREESURFER_HOME') + _old_fs = os.getenv("FREESURFER_HOME") if not _old_fs: - os.environ['FREESURFER_HOME'] = mkdtemp() + os.environ["FREESURFER_HOME"] = mkdtemp() - settings = loads(data.load.readable('tests/config.toml').read_text()) + settings = loads(data.load.readable("tests/config.toml").read_text()) for sectionname, configs in settings.items(): - if sectionname != 'environment': + if sectionname != "environment": section = getattr(config, sectionname) section.load(configs, init=False) config.nipype.omp_nthreads = 1 @@ -52,7 +52,7 @@ def mock_config(bids_dir=None): config.loggers.init() config.init_spaces() - bids_dir = bids_dir or data.load('tests/ds000005').absolute() + bids_dir = bids_dir or data.load("tests/ds000005").absolute() config.execution.work_dir = Path(mkdtemp()) config.execution.bids_dir = bids_dir @@ -67,4 +67,4 @@ def mock_config(bids_dir=None): shutil.rmtree(config.execution.fmriprep_dir) if not _old_fs: - del os.environ['FREESURFER_HOME'] + del os.environ["FREESURFER_HOME"] diff --git a/src/fmripost_aroma/workflows/tests/test_base.py b/src/fmripost_aroma/workflows/tests/test_base.py index 068fcdd..d9a493e 100644 --- a/src/fmripost_aroma/workflows/tests/test_base.py +++ b/src/fmripost_aroma/workflows/tests/test_base.py @@ -16,68 +16,68 @@ from ..tests import mock_config BASE_LAYOUT = { - '01': { - 'anat': [ - {'run': 1, 'suffix': 'T1w'}, - {'run': 2, 'suffix': 'T1w'}, - {'suffix': 'T2w'}, + "01": { + "anat": [ + {"run": 1, "suffix": "T1w"}, + {"run": 2, "suffix": "T1w"}, + {"suffix": "T2w"}, ], - 'func': [ + "func": [ *( { - 'task': 'rest', - 'run': i, - 'suffix': suffix, - 'metadata': { - 'RepetitionTime': 2.0, - 'PhaseEncodingDirection': 'j', - 'TotalReadoutTime': 0.6, - 'EchoTime': 0.03, - 'SliceTiming': [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8], + "task": "rest", + "run": i, + "suffix": suffix, + "metadata": { + "RepetitionTime": 2.0, + "PhaseEncodingDirection": "j", + "TotalReadoutTime": 0.6, + "EchoTime": 0.03, + "SliceTiming": [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8], }, } - for suffix in ('bold', 'sbref') + for suffix in ("bold", "sbref") for i in range(1, 3) ), *( { - 'task': 'nback', - 'echo': i, - 'suffix': 'bold', - 'metadata': { - 'RepetitionTime': 2.0, - 'PhaseEncodingDirection': 'j', - 'TotalReadoutTime': 0.6, - 'EchoTime': 0.015 * i, - 'SliceTiming': [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8], + "task": "nback", + "echo": i, + "suffix": "bold", + "metadata": { + "RepetitionTime": 2.0, + "PhaseEncodingDirection": "j", + "TotalReadoutTime": 0.6, + "EchoTime": 0.015 * i, + "SliceTiming": [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8], }, } for i in range(1, 4) ), ], - 'fmap': [ - {'suffix': 'phasediff', 'metadata': {'EchoTime1': 0.005, 'EchoTime2': 0.007}}, - {'suffix': 'magnitude1', 'metadata': {'EchoTime': 0.005}}, + "fmap": [ + {"suffix": "phasediff", "metadata": {"EchoTime1": 0.005, "EchoTime2": 0.007}}, + {"suffix": "magnitude1", "metadata": {"EchoTime": 0.005}}, { - 'suffix': 'epi', - 'direction': 'PA', - 'metadata': {'PhaseEncodingDirection': 'j', 'TotalReadoutTime': 0.6}, + "suffix": "epi", + "direction": "PA", + "metadata": {"PhaseEncodingDirection": "j", "TotalReadoutTime": 0.6}, }, { - 'suffix': 'epi', - 'direction': 'AP', - 'metadata': {'PhaseEncodingDirection': 'j-', 'TotalReadoutTime': 0.6}, + "suffix": "epi", + "direction": "AP", + "metadata": {"PhaseEncodingDirection": "j-", "TotalReadoutTime": 0.6}, }, ], }, } -@pytest.fixture(scope='module', autouse=True) +@pytest.fixture(scope="module", autouse=True) def _quiet_logger(): import logging - logger = logging.getLogger('nipype.workflow') + logger = logging.getLogger("nipype.workflow") old_level = logger.getEffectiveLevel() logger.setLevel(logging.ERROR) yield @@ -90,22 +90,22 @@ def _reset_sdcflows_registry(): clear_registry() -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def bids_root(tmp_path_factory): - base = tmp_path_factory.mktemp('base') - bids_dir = base / 'bids' + base = tmp_path_factory.mktemp("base") + bids_dir = base / "bids" generate_bids_skeleton(bids_dir, BASE_LAYOUT) img = nb.Nifti1Image(np.zeros((10, 10, 10, 10)), np.eye(4)) - for bold_path in bids_dir.glob('sub-01/*/*.nii.gz'): + for bold_path in bids_dir.glob("sub-01/*/*.nii.gz"): img.to_filename(bold_path) return bids_dir def _make_params( - bold2anat_init: str = 'auto', + bold2anat_init: str = "auto", use_bbr: bool | None = None, dummy_scans: int | None = None, me_output_echos: bool = False, @@ -113,7 +113,7 @@ def _make_params( project_goodvoxels: bool = False, cifti_output: bool | str = False, run_msmsulc: bool = True, - skull_strip_t1w: str = 'auto', + skull_strip_t1w: str = "auto", use_syn_sdc: str | bool = False, force_syn: bool = False, freesurfer: bool = True, @@ -142,45 +142,45 @@ def _make_params( ) -@pytest.mark.parametrize('level', ['minimal', 'resampling', 'full']) -@pytest.mark.parametrize('anat_only', [False, True]) +@pytest.mark.parametrize("level", ["minimal", "resampling", "full"]) +@pytest.mark.parametrize("anat_only", [False, True]) @pytest.mark.parametrize( ( - 'bold2anat_init', - 'use_bbr', - 'dummy_scans', - 'me_output_echos', - 'medial_surface_nan', - 'project_goodvoxels', - 'cifti_output', - 'run_msmsulc', - 'skull_strip_t1w', - 'use_syn_sdc', - 'force_syn', - 'freesurfer', - 'ignore', - 'bids_filters', + "bold2anat_init", + "use_bbr", + "dummy_scans", + "me_output_echos", + "medial_surface_nan", + "project_goodvoxels", + "cifti_output", + "run_msmsulc", + "skull_strip_t1w", + "use_syn_sdc", + "force_syn", + "freesurfer", + "ignore", + "bids_filters", ), [ _make_params(), - _make_params(bold2anat_init='t1w'), - _make_params(bold2anat_init='t2w'), - _make_params(bold2anat_init='header'), + _make_params(bold2anat_init="t1w"), + _make_params(bold2anat_init="t2w"), + _make_params(bold2anat_init="header"), _make_params(use_bbr=True), _make_params(use_bbr=False), - _make_params(bold2anat_init='header', use_bbr=True), + _make_params(bold2anat_init="header", use_bbr=True), # Currently disabled # _make_params(bold2anat_init="header", use_bbr=False), _make_params(dummy_scans=2), _make_params(me_output_echos=True), _make_params(medial_surface_nan=True), - _make_params(cifti_output='91k'), - _make_params(cifti_output='91k', project_goodvoxels=True), - _make_params(cifti_output='91k', project_goodvoxels=True, run_msmsulc=False), - _make_params(cifti_output='91k', run_msmsulc=False), - _make_params(skull_strip_t1w='force'), - _make_params(skull_strip_t1w='skip'), - _make_params(use_syn_sdc='warn', force_syn=True, ignore=['fieldmaps']), + _make_params(cifti_output="91k"), + _make_params(cifti_output="91k", project_goodvoxels=True), + _make_params(cifti_output="91k", project_goodvoxels=True, run_msmsulc=False), + _make_params(cifti_output="91k", run_msmsulc=False), + _make_params(skull_strip_t1w="force"), + _make_params(skull_strip_t1w="skip"), + _make_params(use_syn_sdc="warn", force_syn=True, ignore=["fieldmaps"]), _make_params(freesurfer=False), _make_params(freesurfer=False, use_bbr=True), _make_params(freesurfer=False, use_bbr=False), @@ -189,7 +189,7 @@ def _make_params( # _make_params(freesurfer=False, bold2anat_init="header", use_bbr=True), # _make_params(freesurfer=False, bold2anat_init="header", use_bbr=False), # Regression test for gh-3154: - _make_params(bids_filters={'sbref': {'suffix': 'sbref'}}), + _make_params(bids_filters={"sbref": {"suffix": "sbref"}}), ], ) def test_init_fmriprep_wf( @@ -226,20 +226,20 @@ def test_init_fmriprep_wf( config.workflow.cifti_output = cifti_output config.workflow.run_reconall = freesurfer config.workflow.ignore = ignore - with patch.dict('fmripost_aroma.config.execution.bids_filters', bids_filters): + with patch.dict("fmripost_aroma.config.execution.bids_filters", bids_filters): wf = init_fmriprep_wf() generate_expanded_graph(wf._create_flat_graph()) def test_get_estimator_none(tmp_path): - bids_dir = tmp_path / 'bids' + bids_dir = tmp_path / "bids" # No IntendedFors/B0Fields generate_bids_skeleton(bids_dir, BASE_LAYOUT) layout = bids.BIDSLayout(bids_dir) bold_files = sorted( - layout.get(suffix='bold', task='rest', extension='.nii.gz', return_type='file') + layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") ) assert get_estimator(layout, bold_files[0]) == () @@ -247,78 +247,78 @@ def test_get_estimator_none(tmp_path): def test_get_estimator_b0field_and_intendedfor(tmp_path): - bids_dir = tmp_path / 'bids' + bids_dir = tmp_path / "bids" # Set B0FieldSource for run 1 spec = deepcopy(BASE_LAYOUT) - spec['01']['func'][0]['metadata']['B0FieldSource'] = 'epi' - spec['01']['fmap'][2]['metadata']['B0FieldIdentifier'] = 'epi' - spec['01']['fmap'][3]['metadata']['B0FieldIdentifier'] = 'epi' + spec["01"]["func"][0]["metadata"]["B0FieldSource"] = "epi" + spec["01"]["fmap"][2]["metadata"]["B0FieldIdentifier"] = "epi" + spec["01"]["fmap"][3]["metadata"]["B0FieldIdentifier"] = "epi" # Set IntendedFor for run 2 - spec['01']['fmap'][0]['metadata']['IntendedFor'] = 'func/sub-01_task-rest_run-2_bold.nii.gz' + spec["01"]["fmap"][0]["metadata"]["IntendedFor"] = "func/sub-01_task-rest_run-2_bold.nii.gz" generate_bids_skeleton(bids_dir, spec) layout = bids.BIDSLayout(bids_dir) - _ = find_estimators(layout=layout, subject='01') + _ = find_estimators(layout=layout, subject="01") bold_files = sorted( - layout.get(suffix='bold', task='rest', extension='.nii.gz', return_type='file') + layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") ) - assert get_estimator(layout, bold_files[0]) == ('epi',) - assert get_estimator(layout, bold_files[1]) == ('auto_00000',) + assert get_estimator(layout, bold_files[0]) == ("epi",) + assert get_estimator(layout, bold_files[1]) == ("auto_00000",) def test_get_estimator_overlapping_specs(tmp_path): - bids_dir = tmp_path / 'bids' + bids_dir = tmp_path / "bids" # Set B0FieldSource for both runs spec = deepcopy(BASE_LAYOUT) - spec['01']['func'][0]['metadata']['B0FieldSource'] = 'epi' - spec['01']['func'][1]['metadata']['B0FieldSource'] = 'epi' - spec['01']['fmap'][2]['metadata']['B0FieldIdentifier'] = 'epi' - spec['01']['fmap'][3]['metadata']['B0FieldIdentifier'] = 'epi' + spec["01"]["func"][0]["metadata"]["B0FieldSource"] = "epi" + spec["01"]["func"][1]["metadata"]["B0FieldSource"] = "epi" + spec["01"]["fmap"][2]["metadata"]["B0FieldIdentifier"] = "epi" + spec["01"]["fmap"][3]["metadata"]["B0FieldIdentifier"] = "epi" # Set IntendedFor for both runs - spec['01']['fmap'][0]['metadata']['IntendedFor'] = [ - 'func/sub-01_task-rest_run-1_bold.nii.gz', - 'func/sub-01_task-rest_run-2_bold.nii.gz', + spec["01"]["fmap"][0]["metadata"]["IntendedFor"] = [ + "func/sub-01_task-rest_run-1_bold.nii.gz", + "func/sub-01_task-rest_run-2_bold.nii.gz", ] generate_bids_skeleton(bids_dir, spec) layout = bids.BIDSLayout(bids_dir) - _ = find_estimators(layout=layout, subject='01') + _ = find_estimators(layout=layout, subject="01") bold_files = sorted( - layout.get(suffix='bold', task='rest', extension='.nii.gz', return_type='file') + layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") ) # B0Fields take precedence - assert get_estimator(layout, bold_files[0]) == ('epi',) - assert get_estimator(layout, bold_files[1]) == ('epi',) + assert get_estimator(layout, bold_files[0]) == ("epi",) + assert get_estimator(layout, bold_files[1]) == ("epi",) def test_get_estimator_multiple_b0fields(tmp_path): - bids_dir = tmp_path / 'bids' + bids_dir = tmp_path / "bids" # Set B0FieldSource for both runs spec = deepcopy(BASE_LAYOUT) - spec['01']['func'][0]['metadata']['B0FieldSource'] = ('epi', 'phasediff') - spec['01']['func'][1]['metadata']['B0FieldSource'] = 'epi' - spec['01']['fmap'][0]['metadata']['B0FieldIdentifier'] = 'phasediff' - spec['01']['fmap'][1]['metadata']['B0FieldIdentifier'] = 'phasediff' - spec['01']['fmap'][2]['metadata']['B0FieldIdentifier'] = 'epi' - spec['01']['fmap'][3]['metadata']['B0FieldIdentifier'] = 'epi' + spec["01"]["func"][0]["metadata"]["B0FieldSource"] = ("epi", "phasediff") + spec["01"]["func"][1]["metadata"]["B0FieldSource"] = "epi" + spec["01"]["fmap"][0]["metadata"]["B0FieldIdentifier"] = "phasediff" + spec["01"]["fmap"][1]["metadata"]["B0FieldIdentifier"] = "phasediff" + spec["01"]["fmap"][2]["metadata"]["B0FieldIdentifier"] = "epi" + spec["01"]["fmap"][3]["metadata"]["B0FieldIdentifier"] = "epi" generate_bids_skeleton(bids_dir, spec) layout = bids.BIDSLayout(bids_dir) - _ = find_estimators(layout=layout, subject='01') + _ = find_estimators(layout=layout, subject="01") bold_files = sorted( - layout.get(suffix='bold', task='rest', extension='.nii.gz', return_type='file') + layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") ) # Always get an iterable; don't care if it's a list or tuple - assert get_estimator(layout, bold_files[0]) == ['epi', 'phasediff'] - assert get_estimator(layout, bold_files[1]) == ('epi',) + assert get_estimator(layout, bold_files[0]) == ["epi", "phasediff"] + assert get_estimator(layout, bold_files[1]) == ("epi",) From 468064bd59b068f82f56e511a36d4a35eac063f5 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sun, 5 May 2024 14:09:09 -0400 Subject: [PATCH 03/15] Run isort. --- docs/conf.py | 4 +++- src/fmripost_aroma/interfaces/reportlets.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 90ac86c..65f2a1d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,9 @@ import os import sys -from packaging import version as pver # Avoid distutils.LooseVersion which is deprecated +from packaging import ( + version as pver, # Avoid distutils.LooseVersion which is deprecated +) from sphinx import __version__ as sphinxversion # If extensions (or modules to document with autodoc) are in another directory, diff --git a/src/fmripost_aroma/interfaces/reportlets.py b/src/fmripost_aroma/interfaces/reportlets.py index 5a9fb07..b6207c8 100644 --- a/src/fmripost_aroma/interfaces/reportlets.py +++ b/src/fmripost_aroma/interfaces/reportlets.py @@ -44,7 +44,6 @@ from niworkflows import NIWORKFLOWS_LOG from smriprep.interfaces.freesurfer import ReconAll - SUBJECT_TEMPLATE = """\ \t
    \t\t
  • Subject ID: {subject_id}
  • From 30695ca2bf650fc0ecda36990f61d07343c8a9ec Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sun, 5 May 2024 14:13:16 -0400 Subject: [PATCH 04/15] Fix style issues flagged by ruff. --- src/fmripost_aroma/cli/parser.py | 3 +++ src/fmripost_aroma/cli/run.py | 2 ++ src/fmripost_aroma/config.py | 1 + src/fmripost_aroma/workflows/aroma.py | 4 +++- src/fmripost_aroma/workflows/base.py | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/fmripost_aroma/cli/parser.py b/src/fmripost_aroma/cli/parser.py index aa272c2..28cade6 100644 --- a/src/fmripost_aroma/cli/parser.py +++ b/src/fmripost_aroma/cli/parser.py @@ -21,6 +21,7 @@ # https://www.nipreps.org/community/licensing/ # """Parser.""" + import sys from fmripost_aroma import config @@ -31,6 +32,7 @@ def _build_parser(**kwargs): ``kwargs`` are passed to ``argparse.ArgumentParser`` (mainly useful for debugging). """ + from argparse import Action, ArgumentDefaultsHelpFormatter, ArgumentParser from functools import partial from pathlib import Path @@ -464,6 +466,7 @@ def _bids_filter(value, parser): def parse_args(args=None, namespace=None): """Parse args and run further checks on the command line.""" + import logging parser = _build_parser() diff --git a/src/fmripost_aroma/cli/run.py b/src/fmripost_aroma/cli/run.py index eb92659..ebeaeb9 100644 --- a/src/fmripost_aroma/cli/run.py +++ b/src/fmripost_aroma/cli/run.py @@ -22,6 +22,7 @@ # https://www.nipreps.org/community/licensing/ # """fMRI preprocessing workflow.""" + from fmripost_aroma import config EXITCODE: int = -1 @@ -29,6 +30,7 @@ def main(): """Entry point.""" + import gc import sys from multiprocessing import Manager, Process diff --git a/src/fmripost_aroma/config.py b/src/fmripost_aroma/config.py index eae2ac3..8615933 100644 --- a/src/fmripost_aroma/config.py +++ b/src/fmripost_aroma/config.py @@ -87,6 +87,7 @@ :py:class:`~bids.layout.BIDSLayout`, etc.) """ + import os from multiprocessing import set_start_method diff --git a/src/fmripost_aroma/workflows/aroma.py b/src/fmripost_aroma/workflows/aroma.py index 722721c..e3d58f0 100644 --- a/src/fmripost_aroma/workflows/aroma.py +++ b/src/fmripost_aroma/workflows/aroma.py @@ -21,6 +21,7 @@ # https://www.nipreps.org/community/licensing/ # """fMRIPost-AROMA workflows to run ICA-AROMA.""" + import os from nipype.interfaces import utility as niu @@ -124,6 +125,7 @@ def init_ica_aroma_wf( nonaggr_denoised_file BOLD series with non-aggressive ICA-AROMA denoising applied """ + from nipype.interfaces import fsl from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.utility import TSV2JSON @@ -137,7 +139,7 @@ def init_ica_aroma_wf( [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after removal of non-steady state volumes and spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). -Corresponding "non-aggresively" denoised runs were produced after such +Corresponding "non-aggressively" denoised runs were produced after such smoothing. Additionally, the "aggressive" noise-regressors were collected and placed in the corresponding confounds file. diff --git a/src/fmripost_aroma/workflows/base.py b/src/fmripost_aroma/workflows/base.py index 8e4d5de..de31dfe 100644 --- a/src/fmripost_aroma/workflows/base.py +++ b/src/fmripost_aroma/workflows/base.py @@ -28,6 +28,7 @@ .. autofunction:: init_single_subject_wf """ + import os import sys import warnings From 15a55789c7e2bab3b961f39fa717580ab6724511 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sun, 5 May 2024 14:19:50 -0400 Subject: [PATCH 05/15] Try this. --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b8e87ac..0065a39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,14 @@ dependencies = [ ] dynamic = ["version"] +[project.optional-dependencies] +test = [ + "coverage", + "pytest", + "pytest-cov", + "pytest-env", +] + [project.urls] Documentation = "https://github.com/nipreps/fmripost-aroma#readme" Issues = "https://github.com/nipreps/fmripost-aroma/issues" From cc294d453a554484523480903a3162406457b3a3 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Tue, 7 May 2024 13:22:11 -0400 Subject: [PATCH 06/15] Try using tox Won't work yet. --- .github/workflows/pre-release.yml | 32 ++++++------ .github/workflows/stable.yml | 25 ++++------ tox.ini | 83 +++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 tox.ini diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index f039cb8..9b4d8da 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -12,6 +12,10 @@ defaults: run: shell: bash +# Force tox and pytest to use color +env: + FORCE_COLOR: true + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -23,7 +27,6 @@ jobs: pre-release: # Check pre-releases of dependencies on stable Python runs-on: ${{ matrix.os }} - continue-on-error: true strategy: matrix: os: ['ubuntu-latest'] @@ -31,6 +34,8 @@ jobs: install: ['pip'] check: ['tests'] pip-flags: ['PRE_PIP_FLAGS'] + fail-fast: false + env: INSTALL_TYPE: ${{ matrix.install }} CHECK_TYPE: ${{ matrix.check }} @@ -42,28 +47,23 @@ jobs: with: submodules: recursive fetch-depth: 0 - - name: Install dependencies - run: .maint/ci/install_dependencies.sh - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Display Python version run: python -c "import sys; print(sys.version)" - - name: Create virtual environment - run: .maint/ci/create_venv.sh - - name: Build archive + - name: Install tox run: | - source .maint/ci/build_archive.sh - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - name: Install fMRIPost-AROMA - run: .maint/ci/install.sh - - name: Install extras - run: .maint/ci/install_extras.sh - - name: Run tests - run: .maint/ci/check.sh + python -m pip install --upgrade pip + python -m pip install tox tox-gh-actions + - name: Show tox config + run: tox c + - name: Run tox + run: tox -v --exit-and-dump-after 1200 - uses: codecov/codecov-action@v4 + if: ${{ always() }} with: - file: coverage.xml + files: cov.xml token: ${{ secrets.CODECOV_TOKEN }} - if: ${{ always() }} diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index a9d5664..34a2aa6 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -45,28 +45,23 @@ jobs: with: submodules: recursive fetch-depth: 0 - - name: Install dependencies - run: .maint/ci/install_dependencies.sh - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Display Python version run: python -c "import sys; print(sys.version)" - - name: Create virtual environment - run: .maint/ci/create_venv.sh - - name: Build archive + - name: Install tox run: | - source .maint/ci/build_archive.sh - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - name: Install fMRIPost-AROMA - run: .maint/ci/install.sh - - name: Install extras - run: .maint/ci/install_extras.sh - - name: Run tests - run: .maint/ci/check.sh + python -m pip install --upgrade pip + python -m pip install tox tox-gh-actions + - name: Show tox config + run: tox c + - name: Run tox + run: tox -v --exit-and-dump-after 1200 - uses: codecov/codecov-action@v4 + if: ${{ always() }} with: - file: coverage.xml + files: cov.xml token: ${{ secrets.CODECOV_TOKEN }} - if: ${{ always() }} diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..bb892f3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,83 @@ +[tox] +requires = + tox>=4 +envlist = + py3{8,9,10,11,12}-{full,pre} + py38-min +skip_missing_interpreters = true + +# Configuration that allows us to split tests across GitHub runners effectively +[gh-actions] +python = + 3.10: py310 + 3.11: py311 + 3.12: py312 + +[gh-actions:env] +DEPENDS = + min: min + full: full + pre: pre + +[testenv] +description = Pytest with coverage +labels = test +pip_pre = + pre: true +pass_env = + # getpass.getuser() sources for Windows: + LOGNAME + USER + LNAME + USERNAME + # Pass user color preferences through + PY_COLORS + FORCE_COLOR + NO_COLOR + CLICOLOR + CLICOLOR_FORCE +extras = test +commands = + pytest --doctest-modules -v \ + --cov bids --cov-report xml:cov.xml --cov-report term \ + bids {posargs:-n auto} + +[testenv:docs] +description = Build documentation site +labels = docs +allowlist_externals = make +extras = doc +commands = + make -C doc html + +[testenv:spellcheck] +description = Check spelling +labels = check +deps = + codespell[toml] +skip_install = true +commands = + codespell . {posargs} + +[testenv:build{,-strict}] +labels = + check + pre-release +deps = + build + twine +skip_install = true +set_env = + build-strict: PYTHONWARNINGS=error +commands = + python -m build + python -m twine check dist/* + +[testenv:publish] +depends = build +labels = release +deps = + twine +skip_install = true +commands = + python -m twine upload dist/* From 6d3923e844cd8a67b4ee78cad4f81eb99a533501 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Tue, 7 May 2024 14:09:59 -0400 Subject: [PATCH 07/15] Update test. --- .maint/ci/activate.sh | 9 -------- .maint/ci/build_archive.sh | 34 ------------------------------- .maint/ci/check.sh | 26 ----------------------- .maint/ci/create_venv.sh | 24 ---------------------- .maint/ci/env.sh | 6 ------ .maint/ci/install.sh | 34 ------------------------------- .maint/ci/install_dependencies.sh | 21 ------------------- .maint/ci/install_extras.sh | 24 ---------------------- tox.ini | 5 ++--- 9 files changed, 2 insertions(+), 181 deletions(-) delete mode 100644 .maint/ci/activate.sh delete mode 100755 .maint/ci/build_archive.sh delete mode 100755 .maint/ci/check.sh delete mode 100755 .maint/ci/create_venv.sh delete mode 100644 .maint/ci/env.sh delete mode 100755 .maint/ci/install.sh delete mode 100755 .maint/ci/install_dependencies.sh delete mode 100755 .maint/ci/install_extras.sh diff --git a/.maint/ci/activate.sh b/.maint/ci/activate.sh deleted file mode 100644 index 567e13a..0000000 --- a/.maint/ci/activate.sh +++ /dev/null @@ -1,9 +0,0 @@ -if [ -e virtenv/bin/activate ]; then - source virtenv/bin/activate -elif [ -e virtenv/Scripts/activate ]; then - source virtenv/Scripts/activate -else - echo Cannot activate virtual environment - ls -R virtenv - false -fi diff --git a/.maint/ci/build_archive.sh b/.maint/ci/build_archive.sh deleted file mode 100755 index 6e8be6f..0000000 --- a/.maint/ci/build_archive.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -echo "Building archive" - -source .maint/ci/activate.sh - -set -eu - -# Required dependencies -echo "INSTALL_TYPE = $INSTALL_TYPE" - -set -x - -if [ "$INSTALL_TYPE" = "sdist" -o "$INSTALL_TYPE" = "wheel" ]; then - python -m build -elif [ "$INSTALL_TYPE" = "archive" ]; then - ARCHIVE="/tmp/package.tar.gz" - git archive -o $ARCHIVE HEAD -fi - -if [ "$INSTALL_TYPE" = "sdist" ]; then - ARCHIVE=$( ls $PWD/dist/*.tar.gz ) -elif [ "$INSTALL_TYPE" = "wheel" ]; then - ARCHIVE=$( ls $PWD/dist/*.whl ) -elif [ "$INSTALL_TYPE" = "pip" ]; then - ARCHIVE="$PWD" -fi - -if [ "$INSTALL_TYPE" = "sdist" -o "$INSTALL_TYPE" = "wheel" ]; then - python -m pip install twine - python -m twine check $ARCHIVE -fi - -set +eux diff --git a/.maint/ci/check.sh b/.maint/ci/check.sh deleted file mode 100755 index 7b562ba..0000000 --- a/.maint/ci/check.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -echo Running tests - -source .maint/ci/activate.sh - -set -eu - -# Required variables -echo CHECK_TYPE = $CHECK_TYPE - -set -x - -if [ "${CHECK_TYPE}" == "doc" ]; then - cd doc - make html && make doctest -elif [ "${CHECK_TYPE}" == "tests" ]; then - pytest --doctest-modules --cov fmripost_aroma --cov-report xml \ - --junitxml=test-results.xml -v fmripost_aroma -else - false -fi - -set +eux - -echo Done running tests diff --git a/.maint/ci/create_venv.sh b/.maint/ci/create_venv.sh deleted file mode 100755 index 6b6822e..0000000 --- a/.maint/ci/create_venv.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -echo Creating isolated virtual environment - -source .maint/ci/env.sh - -set -eu - -# Required variables -echo SETUP_REQUIRES = $SETUP_REQUIRES - -set -x - -python -m pip install --upgrade pip virtualenv -virtualenv --python=python virtenv -source .maint/ci/activate.sh -python --version -python -m pip install -U $SETUP_REQUIRES -which python -which pip - -set +eux - -echo Done creating isolated virtual environment diff --git a/.maint/ci/env.sh b/.maint/ci/env.sh deleted file mode 100644 index 65e148b..0000000 --- a/.maint/ci/env.sh +++ /dev/null @@ -1,6 +0,0 @@ -SETUP_REQUIRES="pip build" - -# Numpy and scipy upload nightly/weekly/intermittent wheels -NIGHTLY_WHEELS="https://pypi.anaconda.org/scipy-wheels-nightly/simple" -STAGING_WHEELS="https://pypi.anaconda.org/multibuild-wheels-staging/simple" -PRE_PIP_FLAGS="--pre --extra-index-url $NIGHTLY_WHEELS --extra-index-url $STAGING_WHEELS --prefer-binary" diff --git a/.maint/ci/install.sh b/.maint/ci/install.sh deleted file mode 100755 index b9faf63..0000000 --- a/.maint/ci/install.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -echo Installing fmripost_aroma - -source .maint/ci/activate.sh -source .maint/ci/env.sh - -set -eu - -# Required variables -echo INSTALL_TYPE = $INSTALL_TYPE -echo CHECK_TYPE = $CHECK_TYPE -echo EXTRA_PIP_FLAGS = $EXTRA_PIP_FLAGS - -set -x - -if [ -n "$EXTRA_PIP_FLAGS" ]; then - EXTRA_PIP_FLAGS=${!EXTRA_PIP_FLAGS} -fi - -pip install $EXTRA_PIP_FLAGS $ARCHIVE - -# Basic import check -python -c 'import fmripost_aroma; print(fmripost_aroma.__version__)' - -if [ "$CHECK_TYPE" == "skiptests" ]; then - exit 0 -fi - -pip install $EXTRA_PIP_FLAGS "fmripost_aroma[$CHECK_TYPE]" - -set +eux - -echo Done installing fmripost_aroma diff --git a/.maint/ci/install_dependencies.sh b/.maint/ci/install_dependencies.sh deleted file mode 100755 index 7e0e33f..0000000 --- a/.maint/ci/install_dependencies.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -echo "Installing dependencies" - -set -eu - -# Required variables -echo OS_TYPE = $OS_TYPE - -if [ "$OS_TYPE" = "ubuntu-latest" ]; then - sudo apt update - sudo apt install -y graphviz -elif [ "$OS_TYPE" = "macos-latest" ]; then - brew install graphviz -else - echo "Unknown OS_TYPE: $OS_TYPE" -fi - -set +eux - -echo Done installing dependencies diff --git a/.maint/ci/install_extras.sh b/.maint/ci/install_extras.sh deleted file mode 100755 index 19d4449..0000000 --- a/.maint/ci/install_extras.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -echo Installing dependencies - -source .maint/ci/activate.sh -source .maint/ci/env.sh - -set -eu - -# Required variables -echo EXTRA_PIP_FLAGS = $EXTRA_PIP_FLAGS -echo CHECK_TYPE = $CHECK_TYPE - -set -x - -if [ -n "$EXTRA_PIP_FLAGS" ]; then - EXTRA_PIP_FLAGS=${!EXTRA_PIP_FLAGS} -fi - -pip install $EXTRA_PIP_FLAGS "fmripost_aroma[$CHECK_TYPE]" - -set +eux - -echo Done installing dependencies diff --git a/tox.ini b/tox.ini index bb892f3..9b2b421 100644 --- a/tox.ini +++ b/tox.ini @@ -38,9 +38,8 @@ pass_env = CLICOLOR_FORCE extras = test commands = - pytest --doctest-modules -v \ - --cov bids --cov-report xml:cov.xml --cov-report term \ - bids {posargs:-n auto} + pytest --doctest-modules --cov fmripost_aroma --cov-report xml \ + --junitxml=test-results.xml -v fmripost_aroma [testenv:docs] description = Build documentation site From 2144cb0243d096372fed9b008d0283333b43d6f9 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Tue, 7 May 2024 14:13:57 -0400 Subject: [PATCH 08/15] Update tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9b2b421..85f521a 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,7 @@ pass_env = extras = test commands = pytest --doctest-modules --cov fmripost_aroma --cov-report xml \ - --junitxml=test-results.xml -v fmripost_aroma + --junitxml=test-results.xml -v src/fmripost_aroma [testenv:docs] description = Build documentation site From e5f8cd5f34e0e34dd6add0f2611a8521290d5049 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Tue, 7 May 2024 14:18:53 -0400 Subject: [PATCH 09/15] Update. --- src/fmripost_aroma/utils/bids.py | 2 +- .../workflows/tests/test_base.py | 326 +----------------- 2 files changed, 6 insertions(+), 322 deletions(-) diff --git a/src/fmripost_aroma/utils/bids.py b/src/fmripost_aroma/utils/bids.py index 88f6ad4..7ca2369 100644 --- a/src/fmripost_aroma/utils/bids.py +++ b/src/fmripost_aroma/utils/bids.py @@ -9,7 +9,7 @@ from bids.layout import BIDSLayout -from fmripost_aroma.data import load_data +from fmripost_aroma.data import load as load_data def collect_derivatives( diff --git a/src/fmripost_aroma/workflows/tests/test_base.py b/src/fmripost_aroma/workflows/tests/test_base.py index d9a493e..da4b17c 100644 --- a/src/fmripost_aroma/workflows/tests/test_base.py +++ b/src/fmripost_aroma/workflows/tests/test_base.py @@ -1,324 +1,8 @@ -from copy import deepcopy -from pathlib import Path -from unittest.mock import patch +"""Tests for fmripost_aroma.workflows.""" -import bids -import nibabel as nb -import numpy as np -import pytest -from nipype.pipeline.engine.utils import generate_expanded_graph -from niworkflows.utils.testing import generate_bids_skeleton -from sdcflows.fieldmaps import clear_registry -from sdcflows.utils.wrangler import find_estimators -from ... import config -from ..base import get_estimator, init_fmriprep_wf -from ..tests import mock_config +def test_init_single_subject_wf(): + from fmripost_aroma.workflows.base import init_single_subject_wf -BASE_LAYOUT = { - "01": { - "anat": [ - {"run": 1, "suffix": "T1w"}, - {"run": 2, "suffix": "T1w"}, - {"suffix": "T2w"}, - ], - "func": [ - *( - { - "task": "rest", - "run": i, - "suffix": suffix, - "metadata": { - "RepetitionTime": 2.0, - "PhaseEncodingDirection": "j", - "TotalReadoutTime": 0.6, - "EchoTime": 0.03, - "SliceTiming": [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8], - }, - } - for suffix in ("bold", "sbref") - for i in range(1, 3) - ), - *( - { - "task": "nback", - "echo": i, - "suffix": "bold", - "metadata": { - "RepetitionTime": 2.0, - "PhaseEncodingDirection": "j", - "TotalReadoutTime": 0.6, - "EchoTime": 0.015 * i, - "SliceTiming": [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8], - }, - } - for i in range(1, 4) - ), - ], - "fmap": [ - {"suffix": "phasediff", "metadata": {"EchoTime1": 0.005, "EchoTime2": 0.007}}, - {"suffix": "magnitude1", "metadata": {"EchoTime": 0.005}}, - { - "suffix": "epi", - "direction": "PA", - "metadata": {"PhaseEncodingDirection": "j", "TotalReadoutTime": 0.6}, - }, - { - "suffix": "epi", - "direction": "AP", - "metadata": {"PhaseEncodingDirection": "j-", "TotalReadoutTime": 0.6}, - }, - ], - }, -} - - -@pytest.fixture(scope="module", autouse=True) -def _quiet_logger(): - import logging - - logger = logging.getLogger("nipype.workflow") - old_level = logger.getEffectiveLevel() - logger.setLevel(logging.ERROR) - yield - logger.setLevel(old_level) - - -@pytest.fixture(autouse=True) -def _reset_sdcflows_registry(): - yield - clear_registry() - - -@pytest.fixture(scope="module") -def bids_root(tmp_path_factory): - base = tmp_path_factory.mktemp("base") - bids_dir = base / "bids" - generate_bids_skeleton(bids_dir, BASE_LAYOUT) - - img = nb.Nifti1Image(np.zeros((10, 10, 10, 10)), np.eye(4)) - - for bold_path in bids_dir.glob("sub-01/*/*.nii.gz"): - img.to_filename(bold_path) - - return bids_dir - - -def _make_params( - bold2anat_init: str = "auto", - use_bbr: bool | None = None, - dummy_scans: int | None = None, - me_output_echos: bool = False, - medial_surface_nan: bool = False, - project_goodvoxels: bool = False, - cifti_output: bool | str = False, - run_msmsulc: bool = True, - skull_strip_t1w: str = "auto", - use_syn_sdc: str | bool = False, - force_syn: bool = False, - freesurfer: bool = True, - ignore: list[str] = None, - bids_filters: dict = None, -): - if ignore is None: - ignore = [] - if bids_filters is None: - bids_filters = {} - return ( - bold2anat_init, - use_bbr, - dummy_scans, - me_output_echos, - medial_surface_nan, - project_goodvoxels, - cifti_output, - run_msmsulc, - skull_strip_t1w, - use_syn_sdc, - force_syn, - freesurfer, - ignore, - bids_filters, - ) - - -@pytest.mark.parametrize("level", ["minimal", "resampling", "full"]) -@pytest.mark.parametrize("anat_only", [False, True]) -@pytest.mark.parametrize( - ( - "bold2anat_init", - "use_bbr", - "dummy_scans", - "me_output_echos", - "medial_surface_nan", - "project_goodvoxels", - "cifti_output", - "run_msmsulc", - "skull_strip_t1w", - "use_syn_sdc", - "force_syn", - "freesurfer", - "ignore", - "bids_filters", - ), - [ - _make_params(), - _make_params(bold2anat_init="t1w"), - _make_params(bold2anat_init="t2w"), - _make_params(bold2anat_init="header"), - _make_params(use_bbr=True), - _make_params(use_bbr=False), - _make_params(bold2anat_init="header", use_bbr=True), - # Currently disabled - # _make_params(bold2anat_init="header", use_bbr=False), - _make_params(dummy_scans=2), - _make_params(me_output_echos=True), - _make_params(medial_surface_nan=True), - _make_params(cifti_output="91k"), - _make_params(cifti_output="91k", project_goodvoxels=True), - _make_params(cifti_output="91k", project_goodvoxels=True, run_msmsulc=False), - _make_params(cifti_output="91k", run_msmsulc=False), - _make_params(skull_strip_t1w="force"), - _make_params(skull_strip_t1w="skip"), - _make_params(use_syn_sdc="warn", force_syn=True, ignore=["fieldmaps"]), - _make_params(freesurfer=False), - _make_params(freesurfer=False, use_bbr=True), - _make_params(freesurfer=False, use_bbr=False), - # Currently unsupported: - # _make_params(freesurfer=False, bold2anat_init="header"), - # _make_params(freesurfer=False, bold2anat_init="header", use_bbr=True), - # _make_params(freesurfer=False, bold2anat_init="header", use_bbr=False), - # Regression test for gh-3154: - _make_params(bids_filters={"sbref": {"suffix": "sbref"}}), - ], -) -def test_init_fmriprep_wf( - bids_root: Path, - tmp_path: Path, - level: str, - anat_only: bool, - bold2anat_init: str, - use_bbr: bool | None, - dummy_scans: int | None, - me_output_echos: bool, - medial_surface_nan: bool, - project_goodvoxels: bool, - cifti_output: bool | str, - run_msmsulc: bool, - skull_strip_t1w: str, - use_syn_sdc: str | bool, - force_syn: bool, - freesurfer: bool, - ignore: list[str], - bids_filters: dict, -): - with mock_config(bids_dir=bids_root): - config.workflow.level = level - config.workflow.anat_only = anat_only - config.workflow.bold2anat_init = bold2anat_init - config.workflow.use_bbr = use_bbr - config.workflow.dummy_scans = dummy_scans - config.execution.me_output_echos = me_output_echos - config.workflow.medial_surface_nan = medial_surface_nan - config.workflow.project_goodvoxels = project_goodvoxels - config.workflow.run_msmsulc = run_msmsulc - config.workflow.skull_strip_t1w = skull_strip_t1w - config.workflow.cifti_output = cifti_output - config.workflow.run_reconall = freesurfer - config.workflow.ignore = ignore - with patch.dict("fmripost_aroma.config.execution.bids_filters", bids_filters): - wf = init_fmriprep_wf() - - generate_expanded_graph(wf._create_flat_graph()) - - -def test_get_estimator_none(tmp_path): - bids_dir = tmp_path / "bids" - - # No IntendedFors/B0Fields - generate_bids_skeleton(bids_dir, BASE_LAYOUT) - layout = bids.BIDSLayout(bids_dir) - bold_files = sorted( - layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") - ) - - assert get_estimator(layout, bold_files[0]) == () - assert get_estimator(layout, bold_files[1]) == () - - -def test_get_estimator_b0field_and_intendedfor(tmp_path): - bids_dir = tmp_path / "bids" - - # Set B0FieldSource for run 1 - spec = deepcopy(BASE_LAYOUT) - spec["01"]["func"][0]["metadata"]["B0FieldSource"] = "epi" - spec["01"]["fmap"][2]["metadata"]["B0FieldIdentifier"] = "epi" - spec["01"]["fmap"][3]["metadata"]["B0FieldIdentifier"] = "epi" - - # Set IntendedFor for run 2 - spec["01"]["fmap"][0]["metadata"]["IntendedFor"] = "func/sub-01_task-rest_run-2_bold.nii.gz" - - generate_bids_skeleton(bids_dir, spec) - layout = bids.BIDSLayout(bids_dir) - _ = find_estimators(layout=layout, subject="01") - - bold_files = sorted( - layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") - ) - - assert get_estimator(layout, bold_files[0]) == ("epi",) - assert get_estimator(layout, bold_files[1]) == ("auto_00000",) - - -def test_get_estimator_overlapping_specs(tmp_path): - bids_dir = tmp_path / "bids" - - # Set B0FieldSource for both runs - spec = deepcopy(BASE_LAYOUT) - spec["01"]["func"][0]["metadata"]["B0FieldSource"] = "epi" - spec["01"]["func"][1]["metadata"]["B0FieldSource"] = "epi" - spec["01"]["fmap"][2]["metadata"]["B0FieldIdentifier"] = "epi" - spec["01"]["fmap"][3]["metadata"]["B0FieldIdentifier"] = "epi" - - # Set IntendedFor for both runs - spec["01"]["fmap"][0]["metadata"]["IntendedFor"] = [ - "func/sub-01_task-rest_run-1_bold.nii.gz", - "func/sub-01_task-rest_run-2_bold.nii.gz", - ] - - generate_bids_skeleton(bids_dir, spec) - layout = bids.BIDSLayout(bids_dir) - _ = find_estimators(layout=layout, subject="01") - - bold_files = sorted( - layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") - ) - - # B0Fields take precedence - assert get_estimator(layout, bold_files[0]) == ("epi",) - assert get_estimator(layout, bold_files[1]) == ("epi",) - - -def test_get_estimator_multiple_b0fields(tmp_path): - bids_dir = tmp_path / "bids" - - # Set B0FieldSource for both runs - spec = deepcopy(BASE_LAYOUT) - spec["01"]["func"][0]["metadata"]["B0FieldSource"] = ("epi", "phasediff") - spec["01"]["func"][1]["metadata"]["B0FieldSource"] = "epi" - spec["01"]["fmap"][0]["metadata"]["B0FieldIdentifier"] = "phasediff" - spec["01"]["fmap"][1]["metadata"]["B0FieldIdentifier"] = "phasediff" - spec["01"]["fmap"][2]["metadata"]["B0FieldIdentifier"] = "epi" - spec["01"]["fmap"][3]["metadata"]["B0FieldIdentifier"] = "epi" - - generate_bids_skeleton(bids_dir, spec) - layout = bids.BIDSLayout(bids_dir) - _ = find_estimators(layout=layout, subject="01") - - bold_files = sorted( - layout.get(suffix="bold", task="rest", extension=".nii.gz", return_type="file") - ) - - # Always get an iterable; don't care if it's a list or tuple - assert get_estimator(layout, bold_files[0]) == ["epi", "phasediff"] - assert get_estimator(layout, bold_files[1]) == ("epi",) + wf = init_single_subject_wf(subject_id="01") + assert wf.name == "sub_01_wf" From be1b81aaaee54908956dbeccca3a06fc600f06e5 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Tue, 7 May 2024 14:29:31 -0400 Subject: [PATCH 10/15] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 0065a39..5c5db3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ ] dependencies = [ "fmriprep", + "matplotlib <= 3.8", "nipype >= 1.8.5", "nireports", "niworkflows", From 20895fda8925154142bbb401e01d7120ca56431e Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Tue, 7 May 2024 14:47:39 -0400 Subject: [PATCH 11/15] Update tox.ini --- tox.ini | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tox.ini b/tox.ini index 85f521a..044bc22 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,7 @@ requires = tox>=4 envlist = - py3{8,9,10,11,12}-{full,pre} - py38-min + py3{10,11,12} skip_missing_interpreters = true # Configuration that allows us to split tests across GitHub runners effectively @@ -13,17 +12,9 @@ python = 3.11: py311 3.12: py312 -[gh-actions:env] -DEPENDS = - min: min - full: full - pre: pre - [testenv] description = Pytest with coverage labels = test -pip_pre = - pre: true pass_env = # getpass.getuser() sources for Windows: LOGNAME From d11742ab32df1a63f3df481ffa091ac013e379e8 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 7 May 2024 15:01:52 -0400 Subject: [PATCH 12/15] CI: Run pre-release and stable tests in single workflow --- .github/workflows/pre-release.yml | 69 ---------------------- .github/workflows/{stable.yml => test.yml} | 20 +++---- 2 files changed, 10 insertions(+), 79 deletions(-) delete mode 100644 .github/workflows/pre-release.yml rename .github/workflows/{stable.yml => test.yml} (82%) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml deleted file mode 100644 index 9b4d8da..0000000 --- a/.github/workflows/pre-release.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Pre-release checks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -defaults: - run: - shell: bash - -# Force tox and pytest to use color -env: - FORCE_COLOR: true - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - pre-release: - # Check pre-releases of dependencies on stable Python - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: ['ubuntu-latest'] - python-version: ['3.10', '3.11', '3.12'] - install: ['pip'] - check: ['tests'] - pip-flags: ['PRE_PIP_FLAGS'] - fail-fast: false - - env: - INSTALL_TYPE: ${{ matrix.install }} - CHECK_TYPE: ${{ matrix.check }} - EXTRA_PIP_FLAGS: ${{ matrix.pip-flags }} - OS_TYPE: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - name: Install tox - run: | - python -m pip install --upgrade pip - python -m pip install tox tox-gh-actions - - name: Show tox config - run: tox c - - name: Run tox - run: tox -v --exit-and-dump-after 1200 - - uses: codecov/codecov-action@v4 - if: ${{ always() }} - with: - files: cov.xml - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/stable.yml b/.github/workflows/test.yml similarity index 82% rename from .github/workflows/stable.yml rename to .github/workflows/test.yml index 34a2aa6..1c24c2b 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/test.yml @@ -1,21 +1,25 @@ -name: Stable tests +name: Tests on: push: branches: - main - maint/* - - next pull_request: branches: - main - maint/* - - next + # Allow job to be triggered manually from GitHub interface + workflow_dispatch: defaults: run: shell: bash +# Force tox and pytest to use color +env: + FORCE_COLOR: true + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -31,14 +35,10 @@ jobs: matrix: os: ['ubuntu-latest'] python-version: ['3.10', '3.11', '3.12'] - install: ['pip'] - check: ['tests'] - pip-flags: [''] + dependencies: ['full', 'pre'] + env: - INSTALL_TYPE: ${{ matrix.install }} - CHECK_TYPE: ${{ matrix.check }} - EXTRA_PIP_FLAGS: ${{ matrix.pip-flags }} - OS_TYPE: ${{ matrix.os }} + DEPENDS: ${{ matrix.dependencies }} steps: - uses: actions/checkout@v4 From fdaeac1de293d3541040e1ce433001734a217f1d Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 7 May 2024 15:10:44 -0400 Subject: [PATCH 13/15] CI: Include a minimum dependencies test --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c24c2b..3f4c0df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,6 +36,10 @@ jobs: os: ['ubuntu-latest'] python-version: ['3.10', '3.11', '3.12'] dependencies: ['full', 'pre'] + include: + - os: ubuntu-latest + python-version: '3.10' + dependencies: 'min' env: DEPENDS: ${{ matrix.dependencies }} From 40e8fe12fe5f8dc1a6733aaa4f1dc42db76e1279 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 7 May 2024 15:11:30 -0400 Subject: [PATCH 14/15] TOX: Add min/full/pre markers, drop mpl cap --- pyproject.toml | 1 - tox.ini | 14 +++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5c5db3d..0065a39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ classifiers = [ ] dependencies = [ "fmriprep", - "matplotlib <= 3.8", "nipype >= 1.8.5", "nireports", "niworkflows", diff --git a/tox.ini b/tox.ini index 044bc22..945869d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,8 @@ requires = tox>=4 envlist = - py3{10,11,12} + py3{10,11,12}-{full,pre} + py310-min skip_missing_interpreters = true # Configuration that allows us to split tests across GitHub runners effectively @@ -12,9 +13,17 @@ python = 3.11: py311 3.12: py312 +[gh-actions:env] +DEPENDS = + min: min + full: full + pre: pre + [testenv] description = Pytest with coverage labels = test +pip_pre = + pre: true pass_env = # getpass.getuser() sources for Windows: LOGNAME @@ -28,6 +37,9 @@ pass_env = CLICOLOR CLICOLOR_FORCE extras = test +deps = + min: nipype ==1.8.5 + min: pybids ==0.15.6 commands = pytest --doctest-modules --cov fmripost_aroma --cov-report xml \ --junitxml=test-results.xml -v src/fmripost_aroma From ddddccb417c035e0586d0f1c43f4a4fc933a6680 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 7 May 2024 15:15:50 -0400 Subject: [PATCH 15/15] CI: Formatting --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f4c0df..dc8966d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,10 +36,10 @@ jobs: os: ['ubuntu-latest'] python-version: ['3.10', '3.11', '3.12'] dependencies: ['full', 'pre'] - include: - - os: ubuntu-latest - python-version: '3.10' - dependencies: 'min' + include: + - os: ubuntu-latest + python-version: '3.10' + dependencies: 'min' env: DEPENDS: ${{ matrix.dependencies }}