diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index cfc9dc256f..5290534c06 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] install: [repo] include: - python-version: "3.11" diff --git a/.zenodo.json b/.zenodo.json index eec1b4536c..e58ab57f93 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -52,6 +52,18 @@ "name": "Feczko, Eric", "orcid": "0000-0003-1337-5517", "type": "Researcher" + }, + { + "affiliation": "Department of Biostatistics, Johns Hopkins Bloomberg School of Public Health, MD, USA", + "name": "Sadil, Patrick", + "orcid": "0000-0003-4141-1343", + "type": "Researcher" + }, + { + "affiliation": "Neurospin, CEA, Université Paris-Saclay, CNRS, 91191 Gif-sur-Yvette, France", + "name": "Papadopoulos Orfanos, Dimitri", + "orcid": "0000-0002-1242-8990", + "type": "Researcher" } ], "keywords": [ diff --git a/CHANGES.rst b/CHANGES.rst index ea2c4f7a13..8df7f4fbd4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,53 @@ +0.13.0 (November 20, 2023) +========================== +New feature release in the 0.13.x series. + +This release adds support for MSM-Sulc, improving the alignment of subject +surfaces to the fsLR template. This process is enabled by default, but may +be disabled with the ``--no-msm`` flag. + +The ``--fast-track`` flag has been deprecated in favor of a more flexible +``--derivatives`` flag. This flag can be used to specify one or more +directories to search for derivatives. Derivatives found in these +directories can be used to skip corresponding workflows. For derivatives +that can be deterministically generated from other derivatives, sMRIPrep +will regenerate the derivatives to avoid inconsistencies. + +This supports the 23.2.x series of fMRIPrep, which introduces a ``--level`` +flag to control the level of processing. This feature is not currently +available in sMRIPrep, but will be in a future release. To preview this +functionality, use fMRIPrep's ``--anat-only`` flag to run only structural +workflows. + +* FIX: Add missing fsLR reg sphere to io_spec (#382) +* FIX: Invert sulcal depth metric before passing to MSM, use HCP atlas files (#383) +* FIX: Update surfaces with fsnative2t1w_xfm (#384) +* FIX: Add surface-modify-sphere call to catch potential sphere elongation (#375) +* ENH: Add T2w/FLAIR usage to boilerplate (#392) +* ENH: Annotate mris_expand with thread usage (#386) +* ENH: Add sphere registration to fit workflow, check for precomputed (#370) +* ENH: Save msm registration sphere as desc-msm_sphere.surf.gii (#365) +* ENH: Add Multimodal Surface Matching (#358) +* ENH: Run pytest on CircleCI (#364) +* ENH: Separate surfaces and morphometrics into standalone outputs (#359) +* RF: Split template and fsLR resampling and sinking into isolated workflows (#388) +* RF: Replace most of anat_ribbon_wf with a Python function (#363) +* RF: Break up surface workflows for easier mix-and-match in fMRIPrep (#360) +* TEST: Add smoke tests for main anatomical workflows (#390) +* TEST: Add sloppy MSM configuration for use in debugging/CI (#366) +* DOC: http:// → https:// (#377) +* DOC: Fix misspelling found by codespell (#378) +* MNT: Remove AFNI from smriprep docker container (#387) +* MNT: Use a set literal, not a list literal (#379) +* MNT: Update installation environment (#361) +* MNT: Include 3T18yoSchwartzReactN32 FreeSurfer atlas in image (#357) +* MNT: Infrastructure updates (#351) +* MNT: fix flake8 warning (#349) +* MNT: apply pyupgrade suggestions (#348) +* MNT: fix typos found by codespell (#346) +* MNT: Python 3.11 should be supported (#347) + + 0.12.2 (August 16, 2023) ======================== Bug-fix release in the 0.12.x series. diff --git a/Dockerfile b/Dockerfile index 47d63718f6..08da746b9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,16 +74,14 @@ RUN curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bi ENV MAMBA_ROOT_PREFIX="/opt/conda" COPY env.yml /tmp/env.yml +COPY requirements.txt /tmp/requirements.txt RUN micromamba create -y -f /tmp/env.yml && \ micromamba clean -y -a ENV PATH="/opt/conda/envs/smriprep/bin:$PATH" -RUN /opt/conda/envs/smriprep/bin/npm install -g svgo@^2.8 bids-validator@1.11.0 && \ +RUN /opt/conda/envs/smriprep/bin/npm install -g svgo@^3.0 bids-validator@^1.13 && \ rm -r ~/.npm -COPY requirements.txt /tmp/requirements.txt -RUN /opt/conda/envs/smriprep/bin/pip install --no-cache-dir -r /tmp/requirements.txt - # # Main stage # diff --git a/README.rst b/README.rst index 0d7f2f12f4..aff2b2e7fa 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,7 @@ a combination of tools from well-known software packages, including `FSL `__, `ANTs `__, `FreeSurfer `__, -and `AFNI `__. +and `Connectome Workbench `__. More information and documentation can be found at https://www.nipreps.org/smriprep/. diff --git a/env.yml b/env.yml index 02098ea736..2e99487e37 100644 --- a/env.yml +++ b/env.yml @@ -4,28 +4,31 @@ channels: - conda-forge # Update this ~yearly; last updated April 2023 dependencies: - - python >=3.10,<3.11 + - python=3.10 # Needed for svgo and bids-validator; consider moving to deno - - nodejs=16 + - nodejs=18 # Intel Math Kernel Library for numpy - mkl=2022.1 - mkl-service=2.4 # Base scientific python stack; required by FSL, so pinned here - - numpy=1.25 + - numpy=1.26 - scipy=1.11 - - matplotlib=3.7,!=3.7.2 - - pandas=2.0 - - h5py=3.8 + - matplotlib=3.8 + - pandas=2.1 + - h5py=3.10 # Dependencies compiled against numpy, best to stick with conda - - scikit-image=0.21 + - scikit-image=0.22 - scikit-learn=1.3 # Utilities - graphviz=6.0 - pandoc=3.1 # Workflow dependencies: ANTs - - ants=2.4.4 + - ants=2.5.0 # Workflow dependencies: FSL (versions pinned in 6.0.6.2) - - fsl-bet2=2111.0 - - fsl-flirt=2111.0 - - fsl-fast4=2111.0 + - fsl-bet2=2111.4 + - fsl-flirt=2111.2 + - fsl-fast4=2111.3 - fsl-miscmaths=2203.2 + - pip + - pip: + - -r requirements.txt diff --git a/pyproject.toml b/pyproject.toml index f844c0cdd6..e4c476672d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,14 +12,13 @@ classifiers = [ "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Image Recognition", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] license = {file = "LICENSE"} requires-python = ">=3.10" dependencies = [ - "importlib_resources >= 1.3; python_version < '3.9'", "indexed_gzip >= 0.8.8", "lockfile", "looseversion", diff --git a/requirements.txt b/requirements.txt index 9f3e9466a1..fe4fbe0067 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --resolver=backtracking +# pip-compile --strip-extras # astor==0.8.1 # via formulaic attrs==23.1.0 # via niworkflows -bids-validator==1.12.0 +bids-validator==1.13.1 # via pybids -certifi==2023.7.22 +certifi==2023.11.17 # via requests -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via requests ci-info==0.3.0 # via etelemetry @@ -20,29 +20,29 @@ click==8.1.7 # via # nipype # pybids -contourpy==1.1.0 +contourpy==1.2.0 # via matplotlib -cycler==0.11.0 +cycler==0.12.1 # via matplotlib docopt==0.6.2 # via num2words -etelemetry==0.3.0 +etelemetry==0.3.1 # via nipype -filelock==3.12.2 +filelock==3.13.1 # via nipype -fonttools==4.42.1 +fonttools==4.44.3 # via matplotlib formulaic==0.5.2 # via pybids -greenlet==2.0.2 +greenlet==3.0.1 # via sqlalchemy -h5py==3.9.0 +h5py==3.10.0 # via nitransforms idna==3.4 # via requests -imageio==2.31.2 +imageio==2.32.0 # via scikit-image -indexed-gzip==1.8.3 +indexed-gzip==1.8.7 # via smriprep (pyproject.toml) interface-meta==1.3.0 # via formulaic @@ -72,12 +72,12 @@ lxml==4.9.3 # svgutils markupsafe==2.1.3 # via jinja2 -matplotlib==3.7.1 +matplotlib==3.8.2 # via # niworkflows # seaborn # smriprep (pyproject.toml) -networkx==3.1 +networkx==3.2.1 # via # nipype # prov @@ -90,7 +90,7 @@ nibabel==5.1.0 # niworkflows # pybids # smriprep (pyproject.toml) -nilearn==0.10.1 +nilearn==0.10.2 # via niworkflows nipype==1.8.6 # via @@ -98,11 +98,11 @@ nipype==1.8.6 # smriprep (pyproject.toml) nitransforms==23.0.1 # via niworkflows -niworkflows==1.8.1 +niworkflows==1.9.0 # via smriprep (pyproject.toml) -num2words==0.5.12 +num2words==0.5.13 # via pybids -numpy==1.25.2 +numpy==1.26.2 # via # contourpy # formulaic @@ -116,15 +116,15 @@ numpy==1.25.2 # niworkflows # pandas # pybids - # pywavelets # scikit-image # scikit-learn # scipy # seaborn # smriprep (pyproject.toml) # tifffile -packaging==23.1 +packaging==23.2 # via + # etelemetry # matplotlib # nibabel # nilearn @@ -132,14 +132,14 @@ packaging==23.1 # niworkflows # scikit-image # smriprep (pyproject.toml) -pandas==2.0.3 +pandas==2.1.3 # via # formulaic # nilearn # niworkflows # pybids # seaborn -pillow==10.0.0 +pillow==10.0.1 # via # imageio # matplotlib @@ -153,7 +153,7 @@ pybids==0.16.3 # templateflow pydot==1.4.2 # via nipype -pyparsing==3.0.9 +pyparsing==3.1.1 # via # matplotlib # pydot @@ -164,10 +164,8 @@ python-dateutil==2.8.2 # nipype # pandas # prov -pytz==2023.3 +pytz==2023.3.post1 # via pandas -pywavelets==1.4.1 - # via scikit-image pyyaml==6.0.1 # via # niworkflows @@ -181,11 +179,11 @@ requests==2.31.0 # etelemetry # nilearn # templateflow -scikit-image==0.21.0 +scikit-image==0.22.0 # via niworkflows -scikit-learn==1.3.0 +scikit-learn==1.3.2 # via nilearn -scipy==1.11.2 +scipy==1.11.4 # via # formulaic # nilearn @@ -195,25 +193,25 @@ scipy==1.11.2 # pybids # scikit-image # scikit-learn -seaborn==0.12.2 +seaborn==0.13.0 # via niworkflows -simplejson==3.19.1 +simplejson==3.19.2 # via nipype six==1.16.0 # via # isodate # python-dateutil -sqlalchemy==2.0.20 +sqlalchemy==2.0.23 # via pybids svgutils==0.3.4 # via niworkflows -templateflow==23.0.0 +templateflow==23.1.0 # via # niworkflows # smriprep (pyproject.toml) threadpoolctl==3.2.0 # via scikit-learn -tifffile==2023.8.25 +tifffile==2023.9.26 # via scikit-image tqdm==4.66.1 # via templateflow @@ -223,13 +221,13 @@ traits==6.3.2 # niworkflows transforms3d==0.4.1 # via niworkflows -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # formulaic # sqlalchemy tzdata==2023.3 # via pandas -urllib3==2.0.4 +urllib3==2.1.0 # via requests -wrapt==1.15.0 +wrapt==1.16.0 # via formulaic diff --git a/scripts/fetch_templates.py b/scripts/fetch_templates.py index 5987cced57..3d65ffc143 100755 --- a/scripts/fetch_templates.py +++ b/scripts/fetch_templates.py @@ -89,11 +89,28 @@ def fetch_fsaverage(): tf.get(template, density='164k', suffix='sulc', extension='.shape.gii') +def fetch_fsLR(): + """ + Expected templates: + + tpl-fsLR/tpl-fsLR_hemi-L_den-32k_desc-nomedialwall_dparc.label.gii + tpl-fsLR/tpl-fsLR_hemi-L_den-32k_desc-vaavg_midthickness.shape.gii + tpl-fsLR/tpl-fsLR_hemi-L_den-32k_sphere.surf.gii + tpl-fsLR/tpl-fsLR_hemi-R_den-32k_desc-nomedialwall_dparc.label.gii + tpl-fsLR/tpl-fsLR_hemi-R_den-32k_desc-vaavg_midthickness.shape.gii + tpl-fsLR/tpl-fsLR_hemi-R_den-32k_sphere.surf.gii + 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") + + def fetch_all(): fetch_MNI2009() fetch_MNI6() fetch_OASIS() fetch_fsaverage() + fetch_fsLR() if __name__ == "__main__": diff --git a/smriprep/cli/run.py b/smriprep/cli/run.py index 10ce182814..9647dba1b8 100644 --- a/smriprep/cli/run.py +++ b/smriprep/cli/run.py @@ -445,13 +445,13 @@ def build_workflow(opts, retval): import warnings from time import strftime from subprocess import check_call, CalledProcessError, TimeoutExpired - from pkg_resources import resource_filename as pkgrf import json from bids import BIDSLayout from nipype import logging, config as ncfg from niworkflows.utils.bids import collect_participants from ..__about__ import __version__ + from ..data import load_resource from ..workflows.base import init_smriprep_wf logger = logging.getLogger("nipype.workflow") @@ -628,12 +628,14 @@ def build_workflow(opts, retval): boilerplate, ) + boilerplate_bib = load_resource("boilerplate.bib") + # Generate HTML file resolving citations cmd = [ "pandoc", "-s", "--bibliography", - pkgrf("smriprep", "data/boilerplate.bib"), + str(boilerplate_bib), "--citeproc", "--metadata", 'pagetitle="sMRIPrep citation boilerplate"', @@ -651,7 +653,7 @@ def build_workflow(opts, retval): "pandoc", "-s", "--bibliography", - pkgrf("smriprep", "data/boilerplate.bib"), + str(boilerplate_bib), "--natbib", str(log_dir / "CITATION.md"), "-o", @@ -662,7 +664,7 @@ def build_workflow(opts, retval): except (FileNotFoundError, CalledProcessError, TimeoutExpired): logger.warning("Could not generate CITATION.tex file:\n%s", " ".join(cmd)) else: - copyfile(pkgrf("smriprep", "data/boilerplate.bib"), str(log_dir / "CITATION.bib")) + copyfile(str(boilerplate_bib), str(log_dir / "CITATION.bib")) return retval diff --git a/smriprep/utils/bids.py b/smriprep/utils/bids.py index 5b9174d6dd..8513696c55 100644 --- a/smriprep/utils/bids.py +++ b/smriprep/utils/bids.py @@ -23,16 +23,15 @@ """Utilities to handle BIDS inputs.""" from pathlib import Path from json import loads -from pkg_resources import resource_filename as pkgrf from bids.layout import BIDSLayout +from ..data import load_resource + def collect_derivatives(derivatives_dir, subject_id, std_spaces, spec=None, patterns=None): """Gather existing derivatives and compose a cache.""" if spec is None or patterns is None: - _spec, _patterns = tuple( - loads(Path(pkgrf("smriprep", "data/io_spec.json")).read_text()).values() - ) + _spec, _patterns = tuple(loads(load_resource("io_spec.json").read_text()).values()) if spec is None: spec = _spec @@ -92,11 +91,11 @@ def write_derivative_description(bids_dir, deriv_dir): .. testsetup:: - >>> from pkg_resources import resource_filename + >>> from smriprep.data import load_resource >>> from pathlib import Path >>> from tempfile import TemporaryDirectory >>> tmpdir = TemporaryDirectory() - >>> bids_dir = resource_filename('smriprep', 'data/tests') + >>> bids_dir = load_resource('tests') >>> deriv_desc = Path(tmpdir.name) / 'dataset_description.json' .. doctest:: diff --git a/smriprep/workflows/anatomical.py b/smriprep/workflows/anatomical.py index 73428edeed..76a19b9f7a 100644 --- a/smriprep/workflows/anatomical.py +++ b/smriprep/workflows/anatomical.py @@ -23,8 +23,6 @@ """Anatomical reference preprocessing workflows.""" import typing as ty -from pkg_resources import resource_filename as pkgr - from nipype import logging from nipype.pipeline import engine as pe from nipype.interfaces import ( @@ -50,6 +48,7 @@ from niworkflows.utils.spaces import SpatialReferences, Reference from niworkflows.utils.misc import add_suffix from niworkflows.anat.ants import init_brain_extraction_wf, init_n4_only_wf +from ..data import load_resource from ..interfaces import DerivativesDataSink from ..utils.misc import apply_lut as _apply_bids_lut, fs_isRunning as _fs_isRunning from .fit.registration import init_register_template_wf @@ -1454,7 +1453,7 @@ def init_anat_template_wf( if num_files == 1: get1st = pe.Node(niu.Select(index=[0]), name="get1st") - outputnode.inputs.anat_realign_xfm = [pkgr("smriprep", "data/itkIdentityTransform.txt")] + outputnode.inputs.anat_realign_xfm = [str(load_resource("itkIdentityTransform.txt"))] # fmt:off workflow.connect([