diff --git a/.travis.yml b/.travis.yml index 3e4326c5a4..64641b0f14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,19 @@ +os: linux dist: xenial -sudo: true language: python # our build matrix python: -- 3.5 - 3.6 - 3.7 -# NOTE: Any changes to the matrix section should be duplicated below for -# Python 3.4 env: global: - EXTRA_WHEELS="https://5cf40426d9f06eb7461d-6fe47d9331aba7cd62fc36c7196769e4.ssl.cf2.rackcdn.com" - PRE_WHEELS="https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" - EXTRA_PIP_FLAGS="--find-links=$EXTRA_WHEELS" - CHECK_TYPE=test - matrix: + jobs: - INSTALL_DEB_DEPENDECIES=true NIPYPE_EXTRAS="doc,tests,nipy,profiler" CI_SKIP_TEST=1 @@ -31,7 +28,7 @@ env: EXTRA_PIP_FLAGS="--pre $EXTRA_PIP_FLAGS --find-links $PRE_WHEELS --upgrade" CI_SKIP_TEST=1 -matrix: +jobs: include: - python: 3.7 env: diff --git a/.zenodo.json b/.zenodo.json index 92c6f3ed17..8a57735308 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -157,11 +157,6 @@ "name": "De La Vega, Alejandro", "orcid": "0000-0001-9062-3778" }, - { - "affiliation": "Charite Universitatsmedizin Berlin, Germany", - "name": "Waller, Lea", - "orcid": "0000-0002-3239-6957" - }, { "affiliation": "MIT", "name": "Kaczmarzyk, Jakub", @@ -183,6 +178,11 @@ { "name": "Erickson, Drew" }, + { + "affiliation": "Neuroscience Program, University of Iowa", + "name": "Kent, James D.", + "orcid": "0000-0002-4892-2659" + }, { "affiliation": "Otto-von-Guericke-University Magdeburg, Germany", "name": "Hanke, Michael", @@ -195,11 +195,6 @@ { "name": "Moloney, Brendan" }, - { - "affiliation": "Neuroscience Program, University of Iowa", - "name": "Kent, James D.", - "orcid": "0000-0002-4892-2659" - }, { "affiliation": "SRI International", "name": "Nichols, B. Nolan", @@ -246,6 +241,11 @@ { "name": "Mordom, David" }, + { + "affiliation": "University College London", + "name": "Mancini, Matteo", + "orcid": "0000-0001-7194-4568" + }, { "affiliation": "ARAMIS LAB, Brain and Spine Institute (ICM), Paris, France.", "name": "Guillon, Je\u0301re\u0301my", @@ -291,16 +291,16 @@ { "name": "Salvatore, John" }, - { - "affiliation": "University College London", - "name": "Mancini, Matteo", - "orcid": "0000-0001-7194-4568" - }, { "affiliation": "CNRS LTCI, Telecom ParisTech, Universit\u00e9 Paris-Saclay", "name": "Gramfort, Alexandre", "orcid": "0000-0001-9791-4404" }, + { + "affiliation": "Department of Psychology, University of Bielefeld, Bielefeld, Germany.", + "name": "Doll, Anna", + "orcid": "0000-0002-0799-0831" + }, { "name": "Buchanan, Colin" }, @@ -393,9 +393,14 @@ "orcid": "0000-0003-2766-8425" }, { - "affiliation": "Department of Psychology, University of Bielefeld, Bielefeld, Germany.", - "name": "Doll, Anna", - "orcid": "0000-0002-0799-0831" + "affiliation": "NIMH, Scientific and Statistical Computing Core", + "name": "Glen, Daniel", + "orcid": "0000-0001-8456-5647" + }, + { + "affiliation": "Technische Universit\u00e4t Dresden, Faculty of Medicine, Department of Child and Adolescent Psychiatry", + "name": "Geisler, Daniel", + "orcid": "0000-0003-2076-5329" }, { "affiliation": "University of Iowa", @@ -406,6 +411,11 @@ "name": "Triplett, William", "orcid": "0000-0002-9546-1306" }, + { + "affiliation": "The University of Iowa", + "name": "Ghayoor, Ali", + "orcid": "0000-0002-8858-1254" + }, { "affiliation": "Child Mind Institute", "name": "Craddock, R. Cameron", @@ -450,12 +460,12 @@ "name": "Rothmei, Simon" }, { - "name": "Weinstein, Alejandro" + "affiliation": "Korea Advanced Institute of Science and Technology", + "name": "Kim, Sin", + "orcid": "0000-0003-4652-3758" }, { - "affiliation": "The University of Iowa", - "name": "Ghayoor, Ali", - "orcid": "0000-0002-8858-1254" + "name": "Weinstein, Alejandro" }, { "affiliation": "University of Pennsylvania", @@ -508,6 +518,11 @@ "name": "Linkersd\u00f6rfer, Janosch", "orcid": "0000-0002-1577-1233" }, + { + "affiliation": "Charite Universitatsmedizin Berlin, Germany", + "name": "Waller, Lea", + "orcid": "0000-0002-3239-6957" + }, { "name": "Renfro, Mandy" }, @@ -521,11 +536,6 @@ { "name": "K\u00fcttner, Ren\u00e9" }, - { - "affiliation": "Korea Advanced Institute of Science and Technology", - "name": "Kim, Sin", - "orcid": "0000-0003-4652-3758" - }, { "affiliation": "California Institute of Technology", "name": "Pauli, Wolfgang M.", @@ -536,6 +546,11 @@ "name": "Glen, Daniel", "orcid": "0000-0001-8456-5647" }, + { + "affiliation": "Florida International University", + "name": "Kimbler, Adam", + "orcid": "0000-0001-5885-9596" + }, { "affiliation": "University of Pittsburgh", "name": "Meyers, Benjamin", @@ -544,11 +559,6 @@ { "name": "Tarbert, Claire" }, - { - "affiliation": "Technische Universit\u00e4t Dresden, Faculty of Medicine, Department of Child and Adolescent Psychiatry", - "name": "Geisler, Daniel", - "orcid": "0000-0003-2076-5329" - }, { "name": "Ginsburg, Daniel" }, diff --git a/doc/changelog/1.X.X-changelog.rst b/doc/changelog/1.X.X-changelog.rst index 3da15d7a68..239aa7d936 100644 --- a/doc/changelog/1.X.X-changelog.rst +++ b/doc/changelog/1.X.X-changelog.rst @@ -1,3 +1,29 @@ +1.5.0 (To be determined) +========================= + +New feature release in the 1.5.x series. + +In this release, the example scripts have been split out into their own package: +`niflow-nipype1-examples `__. + +(`Full changelog `__) + + * FIX: Partial rollback of N4BiasFieldCorrection (https://github.com/nipy/nipype/pull/3188) + * FIX: ANTs' tools maintenance overhaul (https://github.com/nipy/nipype/pull/3180) + * FIX: load_resultfile crashes if open resultsfile from crashed job (https://github.com/nipy/nipype/pull/3182) + * FIX: FSL model.py make multiple F-tests (https://github.com/nipy/nipype/pull/3166) + * ENH: Improve workflow connect performance (https://github.com/nipy/nipype/pull/3184) + * ENH: Add ``ConstrainedSphericalDeconvolution`` interface to replace ``EstimateFOD`` for MRtrix3's ``dwi2fod`` (https://github.com/nipy/nipype/pull/3176) + * ENH: Detect values for EulerNumber interface (https://github.com/nipy/nipype/pull/3173) + * ENH: Remove examples from repository (https://github.com/nipy/nipype/pull/3172) + * REF: Prefer math.gcd to hand-rolled Euclid's algorithm (https://github.com/nipy/nipype/pull/3177) + * REF: Removed all uses of numpy_mmap (https://github.com/nipy/nipype/pull/3121) + * DOC: Update links, typos in contributing guide (https://github.com/nipy/nipype/pull/3160) + * DOC: Update SelectFiles docstring to match actual behavior (https://github.com/nipy/nipype/pull/3041) + * DOC: Updated .zenodo.json file (https://github.com/nipy/nipype/pull/3167) + * DOC: Update .zenodo.json (https://github.com/nipy/nipype/pull/3165) + * MNT: Update Zenodo ordering based on commit count (https://github.com/nipy/nipype/pull/3169) + 1.4.2 (February 14, 2020) ========================= (`Full changelog `__) diff --git a/nipype/__init__.py b/nipype/__init__.py index 43e9011175..72b7241020 100644 --- a/nipype/__init__.py +++ b/nipype/__init__.py @@ -83,41 +83,9 @@ def check_latest_version(raise_exception=False): import etelemetry logger = logging.getLogger("nipype.utils") - - INIT_MSG = "Running {packname} version {version} (latest: {latest})".format - - latest = {"version": "Unknown", "bad_versions": []} - result = None - try: - result = etelemetry.get_project("nipy/nipype") - except Exception as e: - logger.warning("Could not check for version updates: \n%s", e) - finally: - if result: - latest.update(**result) - if LooseVersion(__version__) != LooseVersion(latest["version"]): - logger.info( - INIT_MSG( - packname="nipype", version=__version__, latest=latest["version"] - ) - ) - else: - logger.info("No new version available.") - if latest["bad_versions"] and any( - [ - LooseVersion(__version__) == LooseVersion(ver) - for ver in latest["bad_versions"] - ] - ): - message = ( - "You are using a version of Nipype with a critical " - "bug. Please use a different version." - ) - if raise_exception: - raise RuntimeError(message) - else: - logger.critical(message) - return latest + return etelemetry.check_available_version( + "nipy/nipype", __version__, logger, raise_exception + ) # Run telemetry on import for interactive sessions, such as IPython, Jupyter notebooks, Python REPL diff --git a/nipype/algorithms/tests/test_TSNR.py b/nipype/algorithms/tests/test_TSNR.py index e00bf35e05..b9de248155 100644 --- a/nipype/algorithms/tests/test_TSNR.py +++ b/nipype/algorithms/tests/test_TSNR.py @@ -7,7 +7,7 @@ import pytest import numpy.testing as npt -import mock +from unittest import mock import nibabel as nb import numpy as np import os @@ -16,9 +16,7 @@ class TestTSNR: """ Note: Tests currently do a poor job of testing functionality """ - in_filenames = { - "in_file": "tsnrinfile.nii", - } + in_filenames = {"in_file": "tsnrinfile.nii"} out_filenames = { # default output file names "detrended_file": "detrend.nii.gz", diff --git a/nipype/info.py b/nipype/info.py index 7a2e4ae70e..099b53b735 100644 --- a/nipype/info.py +++ b/nipype/info.py @@ -5,7 +5,7 @@ # nipype version information # Remove -dev for release -__version__ = "1.5.0-dev" +__version__ = "1.5.0-rc1.post-dev" def get_nipype_gitversion(): @@ -54,12 +54,11 @@ def get_nipype_gitversion(): "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering", ] -PYTHON_REQUIRES = ">= 3.5" +PYTHON_REQUIRES = ">= 3.6" description = "Neuroimaging in Python: Pipelines and Interfaces" @@ -100,7 +99,7 @@ def get_nipype_gitversion(): # versions NIBABEL_MIN_VERSION = "2.1.0" NETWORKX_MIN_VERSION = "1.9" -NUMPY_MIN_VERSION = "1.12" +NUMPY_MIN_VERSION = "1.13" # Numpy bug in python 3.7: # https://www.opensourceanswers.com/blog/you-shouldnt-use-python-37-for-data-science-right-now.html NUMPY_MIN_VERSION_37 = "1.15.3" @@ -148,7 +147,7 @@ def get_nipype_gitversion(): "simplejson>=%s" % SIMPLEJSON_MIN_VERSION, "traits>=%s,!=5.0" % TRAITS_MIN_VERSION, "filelock>=3.0.0", - "etelemetry", + "etelemetry>=0.2.0", ] # neurdflib has to come after prov @@ -158,7 +157,6 @@ def get_nipype_gitversion(): TESTS_REQUIRES = [ "codecov", "coverage<5", - "mock", "pytest", "pytest-cov", "pytest-env", diff --git a/nipype/interfaces/ants/registration.py b/nipype/interfaces/ants/registration.py index cb8e363c04..b9b6700dcd 100644 --- a/nipype/interfaces/ants/registration.py +++ b/nipype/interfaces/ants/registration.py @@ -591,12 +591,18 @@ class RegistrationOutputSpec(TraitedSpec): forward_transforms = traits.List( File(exists=True), desc="List of output transforms for forward registration" ) + reverse_forward_transforms = traits.List( + File(exists=True), desc="List of output transforms for forward registration reversed for antsApplyTransform" + ) reverse_transforms = traits.List( File(exists=True), desc="List of output transforms for reverse registration" ) forward_invert_flags = traits.List( traits.Bool(), desc="List of flags corresponding to the forward transforms" ) + reverse_forward_invert_flags = traits.List( + traits.Bool(), desc="List of flags corresponding to the forward transforms reversed for antsApplyTransform" + ) reverse_invert_flags = traits.List( traits.Bool(), desc="List of flags corresponding to the reverse transforms" ) @@ -797,6 +803,8 @@ class Registration(ANTSCommand): 'inverse_composite_transform': '...data/output_InverseComposite.h5', 'inverse_warped_image': , 'metric_value': , + 'reverse_forward_invert_flags': [], + 'reverse_forward_transforms': [], 'reverse_invert_flags': [], 'reverse_transforms': [], 'save_state': '...data/trans.mat', @@ -826,6 +834,9 @@ class Registration(ANTSCommand): 'inverse_composite_transform': , 'inverse_warped_image': , 'metric_value': , + 'reverse_forward_invert_flags': [False, False], + 'reverse_forward_transforms': ['...data/output_1Warp.nii.gz', + '...data/output_0GenericAffine.mat'], 'reverse_invert_flags': [True, False], 'reverse_transforms': ['...data/output_0GenericAffine.mat', \ '...data/output_1InverseWarp.nii.gz'], @@ -1472,6 +1483,10 @@ def _list_outputs(self): outputs["metric_value"] = self._metric_value if self._elapsed_time: outputs["elapsed_time"] = self._elapsed_time + + outputs["reverse_forward_transforms"] = outputs["forward_transforms"][::-1] + outputs["reverse_forward_invert_flags"] = outputs["forward_invert_flags"][::-1] + return outputs diff --git a/nipype/interfaces/ants/tests/test_auto_Registration.py b/nipype/interfaces/ants/tests/test_auto_Registration.py index 33921e8638..151a852820 100644 --- a/nipype/interfaces/ants/tests/test_auto_Registration.py +++ b/nipype/interfaces/ants/tests/test_auto_Registration.py @@ -97,6 +97,8 @@ def test_Registration_outputs(): inverse_composite_transform=dict(extensions=None,), inverse_warped_image=dict(extensions=None,), metric_value=dict(), + reverse_forward_invert_flags=dict(), + reverse_forward_transforms=dict(), reverse_invert_flags=dict(), reverse_transforms=dict(), save_state=dict(extensions=None,), diff --git a/nipype/interfaces/base/tests/test_core.py b/nipype/interfaces/base/tests/test_core.py index d7e2620c9b..e97a5bab79 100644 --- a/nipype/interfaces/base/tests/test_core.py +++ b/nipype/interfaces/base/tests/test_core.py @@ -5,6 +5,7 @@ import simplejson as json import pytest +from unittest import mock from .... import config from ....testing import example_data @@ -456,17 +457,18 @@ def test_global_CommandLine_output(tmpdir): ci = BET() assert ci.terminal_output == "stream" # default case - nib.CommandLine.set_default_terminal_output("allatonce") - ci = nib.CommandLine(command="ls -l") - assert ci.terminal_output == "allatonce" + with mock.patch.object(nib.CommandLine, '_terminal_output'): + nib.CommandLine.set_default_terminal_output("allatonce") + ci = nib.CommandLine(command="ls -l") + assert ci.terminal_output == "allatonce" - nib.CommandLine.set_default_terminal_output("file") - ci = nib.CommandLine(command="ls -l") - assert ci.terminal_output == "file" + nib.CommandLine.set_default_terminal_output("file") + ci = nib.CommandLine(command="ls -l") + assert ci.terminal_output == "file" - # Check default affects derived interfaces - ci = BET() - assert ci.terminal_output == "file" + # Check default affects derived interfaces + ci = BET() + assert ci.terminal_output == "file" def test_CommandLine_prefix(tmpdir): diff --git a/nipype/interfaces/base/traits_extension.py b/nipype/interfaces/base/traits_extension.py index cbfe24e676..e3b54eb7cb 100644 --- a/nipype/interfaces/base/traits_extension.py +++ b/nipype/interfaces/base/traits_extension.py @@ -26,6 +26,7 @@ import traits.api as traits from traits.api import TraitType, Unicode from traits.trait_base import _Undefined + try: # Moved in traits 6.0 from traits.trait_type import NoDefaultSpecified diff --git a/nipype/interfaces/freesurfer/utils.py b/nipype/interfaces/freesurfer/utils.py index 5a4d6ca425..4a526cdca8 100644 --- a/nipype/interfaces/freesurfer/utils.py +++ b/nipype/interfaces/freesurfer/utils.py @@ -2592,8 +2592,10 @@ class EulerNumberInputSpec(FSTraitedSpec): class EulerNumberOutputSpec(TraitedSpec): - euler = traits.Int(desc="Euler number of cortical surface. A value of 2 signals a " - "topologically correct surface model with no holes") + euler = traits.Int( + desc="Euler number of cortical surface. A value of 2 signals a " + "topologically correct surface model with no holes" + ) defects = traits.Int(desc="Number of defects") @@ -2621,7 +2623,7 @@ def _run_interface(self, runtime): def _parse_output(self, stdout, stderr): """Parse stdout / stderr and extract defects""" - m = re.search(r'(?<=total defect index = )\d+', stdout or stderr) + m = re.search(r"(?<=total defect index = )\d+", stdout or stderr) if m is None: raise RuntimeError("Could not fetch defect index") self._defects = int(m.group()) diff --git a/nipype/interfaces/fsl/tests/test_base.py b/nipype/interfaces/fsl/tests/test_base.py index 52f93b545f..b030a28a18 100644 --- a/nipype/interfaces/fsl/tests/test_base.py +++ b/nipype/interfaces/fsl/tests/test_base.py @@ -13,8 +13,7 @@ @pytest.mark.skipif(no_fsl(), reason="fsl is not installed") def test_fslversion(): ver = fsl.Info.version() - ver = ver.split(".") - assert ver[0] in ["4", "5"] + assert ver.split(".", 1)[0].isdigit() @pytest.mark.skipif(no_fsl(), reason="fsl is not installed") diff --git a/nipype/interfaces/fsl/tests/test_model.py b/nipype/interfaces/fsl/tests/test_model.py index 456e7b6492..8c12f04fa4 100644 --- a/nipype/interfaces/fsl/tests/test_model.py +++ b/nipype/interfaces/fsl/tests/test_model.py @@ -7,18 +7,25 @@ import nipype.interfaces.fsl.model as fsl from nipype.interfaces.fsl import no_fsl from pathlib import Path -from ....pipeline import engine as pe +from ....pipeline import engine as pe @pytest.mark.skipif(no_fsl(), reason="fsl is not installed") def test_MultipleRegressDesign(tmpdir): - designer = pe.Node(fsl.MultipleRegressDesign(), name='designer', base_dir=str(tmpdir)) + designer = pe.Node( + fsl.MultipleRegressDesign(), name="designer", base_dir=str(tmpdir) + ) designer.inputs.regressors = dict( voice_stenght=[1, 1, 1], age=[0.2, 0.4, 0.5], BMI=[1, -1, 2] ) con1 = ["voice_and_age", "T", ["age", "voice_stenght"], [0.5, 0.5]] con2 = ["just_BMI", "T", ["BMI"], [1]] - designer.inputs.contrasts = [con1, con2, ["con3", "F", [con1, con2]], ["con4", "F", [con2]]] + designer.inputs.contrasts = [ + con1, + con2, + ["con3", "F", [con1, con2]], + ["con4", "F", [con2]], + ] res = designer.run() outputs = res.outputs.get_traitsfree() @@ -27,7 +34,9 @@ def test_MultipleRegressDesign(tmpdir): expected_content = {} - expected_content["design_mat"] = """/NumWaves 3 + expected_content[ + "design_mat" + ] = """/NumWaves 3 /NumPoints 3 /PPheights 3.000000e+00 5.000000e-01 1.000000e+00 @@ -37,7 +46,9 @@ def test_MultipleRegressDesign(tmpdir): 2.000000e+00 5.000000e-01 1.000000e+00 """ - expected_content["design_con"] = """/ContrastName1 voice_and_age + expected_content[ + "design_con" + ] = """/ContrastName1 voice_and_age /ContrastName2 just_BMI /NumWaves 3 /NumContrasts 2 @@ -49,7 +60,9 @@ def test_MultipleRegressDesign(tmpdir): 1.000000e+00 0.000000e+00 0.000000e+00 """ - expected_content["design_fts"] = """/NumWaves 2 + expected_content[ + "design_fts" + ] = """/NumWaves 2 /NumContrasts 2 /Matrix @@ -57,7 +70,9 @@ def test_MultipleRegressDesign(tmpdir): 0 1 """ - expected_content["design_grp"] = """/NumWaves 1 + expected_content[ + "design_grp" + ] = """/NumWaves 1 /NumPoints 3 /Matrix diff --git a/nipype/interfaces/io.py b/nipype/interfaces/io.py index 5dd977dfd9..d9b578caaa 100644 --- a/nipype/interfaces/io.py +++ b/nipype/interfaces/io.py @@ -450,7 +450,7 @@ def _check_s3_base_dir(self): s3_flag = base_directory.lower().startswith(s3_str) if s3_flag: - bucket_name = base_directory[len(s3_str):].partition('/')[0] + bucket_name = base_directory[len(s3_str) :].partition("/")[0] return s3_flag, bucket_name @@ -610,7 +610,7 @@ def _upload_to_s3(self, bucket, src, dst): # Explicitly lower-case the "s3" if dst.lower().startswith(s3_str): - dst = s3_str + dst[len(s3_str):] + dst = s3_str + dst[len(s3_str) :] # If src is a directory, collect files (this assumes dst is a dir too) if os.path.isdir(src): @@ -3005,7 +3005,9 @@ class ExportFileInputSpec(BaseInterfaceInputSpec): in_file = File(exists=True, mandatory=True, desc="Input file name") out_file = File(mandatory=True, desc="Output file name") check_extension = traits.Bool( - True, desc="Ensure that the input and output file extensions match" + True, + usedefault=True, + desc="Ensure that the input and output file extensions match", ) clobber = traits.Bool(desc="Permit overwriting existing files") diff --git a/nipype/interfaces/niftyreg/base.py b/nipype/interfaces/niftyreg/base.py index aa343dcfcb..88e441d52a 100644 --- a/nipype/interfaces/niftyreg/base.py +++ b/nipype/interfaces/niftyreg/base.py @@ -19,7 +19,8 @@ import os from ... import logging -from ..base import CommandLine, CommandLineInputSpec, traits, Undefined +from ..base import (CommandLine, CommandLineInputSpec, traits, Undefined, + PackageInfo) from ...utils.filemanip import split_filename iflogger = logging.getLogger("nipype.interface") @@ -29,6 +30,14 @@ def get_custom_path(command, env_dir="NIFTYREGDIR"): return os.path.join(os.getenv(env_dir, ""), command) +class Info(PackageInfo): + version_cmd = get_custom_path('reg_aladin') + ' --version' + + @staticmethod + def parse_version(raw_info): + return raw_info + + class NiftyRegCommandInputSpec(CommandLineInputSpec): """Input Spec for niftyreg interfaces.""" @@ -55,9 +64,8 @@ def __init__(self, required_version=None, **inputs): self.num_threads = 1 super(NiftyRegCommand, self).__init__(**inputs) self.required_version = required_version - _version = self.version_from_command() + _version = self.version if _version: - _version = _version.decode("utf-8") if self._min_version is not None and StrictVersion( _version ) < StrictVersion(self._min_version): @@ -91,11 +99,9 @@ def _environ_update(self): self.inputs.omp_core_val = Undefined def check_version(self): - _version = self.version_from_command() + _version = self.version if not _version: raise Exception("Niftyreg not found") - # Decoding to string: - _version = _version.decode("utf-8") if StrictVersion(_version) < StrictVersion(self._min_version): err = "A later version of Niftyreg is required (%s < %s)" raise ValueError(err % (_version, self._min_version)) @@ -107,10 +113,10 @@ def check_version(self): @property def version(self): - return self.version_from_command() + return Info.version() def exists(self): - return self.version_from_command() is not None + return self.version is not None def _format_arg(self, name, spec, value): if name == "omp_core_val": diff --git a/nipype/interfaces/tests/test_auto_ExportFile.py b/nipype/interfaces/tests/test_auto_ExportFile.py index 8dd84b29b9..331b51d8c2 100644 --- a/nipype/interfaces/tests/test_auto_ExportFile.py +++ b/nipype/interfaces/tests/test_auto_ExportFile.py @@ -4,7 +4,7 @@ def test_ExportFile_inputs(): input_map = dict( - check_extension=dict(), + check_extension=dict(usedefault=True,), clobber=dict(), in_file=dict(extensions=None, mandatory=True,), out_file=dict(extensions=None, mandatory=True,), diff --git a/nipype/interfaces/tests/test_extra_dcm2nii.py b/nipype/interfaces/tests/test_extra_dcm2nii.py index e76f300ec6..0b0c132f7d 100644 --- a/nipype/interfaces/tests/test_extra_dcm2nii.py +++ b/nipype/interfaces/tests/test_extra_dcm2nii.py @@ -22,7 +22,7 @@ def _fetch_data(datadir, dicoms): """Fetches some test DICOMs using datalad""" api.install(path=datadir, source=DICOM_DIR) data = os.path.join(datadir, dicoms) - api.get(path=data) + api.get(path=data, dataset=datadir) except IncompleteResultsError as exc: pytest.skip("Failed to fetch test data: %s" % str(exc)) return data @@ -32,7 +32,6 @@ def _fetch_data(datadir, dicoms): @pytest.mark.skipif(no_datalad, reason="Datalad required") @pytest.mark.skipif(no_dcm2niix, reason="Dcm2niix required") -@pytest.mark.xfail(reason="Intermittent failures. Let's come back to this later.") def test_dcm2niix_dti(fetch_data, tmpdir): tmpdir.chdir() datadir = tmpdir.mkdir("data").strpath diff --git a/nipype/pipeline/engine/workflows.py b/nipype/pipeline/engine/workflows.py index bc532ddf90..0f50b9a5aa 100644 --- a/nipype/pipeline/engine/workflows.py +++ b/nipype/pipeline/engine/workflows.py @@ -803,7 +803,8 @@ def _check_is_already_connected(workflow, node, attrname): if subtype == "in": hierattrname = ".".join(hierarchy + [nodename, attrname]) if not _check_is_already_connected( - targetworkflow, workflow, hierattrname): + targetworkflow, workflow, hierattrname + ): return False targetworkflow = workflow @@ -828,8 +829,7 @@ def _check_is_already_connected(workflow, node, attrname): # Verify input does not already have an incoming connection # in the target workflow if subtype == "in": - if not _check_is_already_connected( - targetworkflow, targetnode, attrname): + if not _check_is_already_connected(targetworkflow, targetnode, attrname): return False return True diff --git a/nipype/pipeline/plugins/tests/test_oar.py b/nipype/pipeline/plugins/tests/test_oar.py index 1024daaef9..cd3bf9606b 100644 --- a/nipype/pipeline/plugins/tests/test_oar.py +++ b/nipype/pipeline/plugins/tests/test_oar.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -import os -from shutil import rmtree -from tempfile import mkdtemp +from shutil import which import nipype.interfaces.base as nib import pytest @@ -31,13 +29,9 @@ def _list_outputs(self): return outputs -@pytest.mark.xfail(reason="not known") -def test_run_oar(): - cur_dir = os.getcwd() - temp_dir = mkdtemp(prefix="test_engine_", dir=os.getcwd()) - os.chdir(temp_dir) - - pipe = pe.Workflow(name="pipe") +@pytest.mark.skipif(which('oarsub') is None, reason="OAR not installed") +def test_run_pbsgraph(tmp_path): + pipe = pe.Workflow(name="pipe", base_dir=str(tmp_path)) mod1 = pe.Node(interface=OarTestInterface(), name="mod1") mod2 = pe.MapNode(interface=OarTestInterface(), iterfield=["input1"], name="mod2") pipe.connect([(mod1, mod2, [("output1", "input1")])]) @@ -48,5 +42,3 @@ def test_run_oar(): node = list(execgraph.nodes())[names.index("pipe.mod1")] result = node.get_output("output1") assert result == [1, 1] - os.chdir(cur_dir) - rmtree(temp_dir) diff --git a/nipype/pipeline/plugins/tests/test_pbs.py b/nipype/pipeline/plugins/tests/test_pbs.py index bb85443940..64d0d77b5d 100644 --- a/nipype/pipeline/plugins/tests/test_pbs.py +++ b/nipype/pipeline/plugins/tests/test_pbs.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -import os -from shutil import rmtree -from tempfile import mkdtemp -from time import sleep +from shutil import which import nipype.interfaces.base as nib import pytest @@ -32,22 +29,15 @@ def _list_outputs(self): return outputs -@pytest.mark.xfail(reason="not known") -def test_run_pbsgraph(): - cur_dir = os.getcwd() - temp_dir = mkdtemp(prefix="test_engine_") - os.chdir(temp_dir) - - pipe = pe.Workflow(name="pipe") +@pytest.mark.skipif(which('qsub') is None, reason="PBS not installed") +def test_run_pbsgraph(tmp_path): + pipe = pe.Workflow(name="pipe", base_dir=str(tmp_path)) mod1 = pe.Node(interface=PbsTestInterface(), name="mod1") mod2 = pe.MapNode(interface=PbsTestInterface(), iterfield=["input1"], name="mod2") pipe.connect([(mod1, mod2, [("output1", "input1")])]) - pipe.base_dir = os.getcwd() mod1.inputs.input1 = 1 execgraph = pipe.run(plugin="PBSGraph") names = [".".join((node._hierarchy, node.name)) for node in execgraph.nodes()] node = list(execgraph.nodes())[names.index("pipe.mod1")] result = node.get_output("output1") assert result == [1, 1] - os.chdir(cur_dir) - rmtree(temp_dir) diff --git a/nipype/pipeline/plugins/tests/test_tools.py b/nipype/pipeline/plugins/tests/test_tools.py index 17b435bbb6..b1ff7e09ac 100644 --- a/nipype/pipeline/plugins/tests/test_tools.py +++ b/nipype/pipeline/plugins/tests/test_tools.py @@ -7,7 +7,7 @@ import scipy.sparse as ssp import re -import mock +from unittest import mock from nipype.pipeline.plugins.tools import report_crash diff --git a/nipype/testing/tests/test_utils.py b/nipype/testing/tests/test_utils.py index fb2992b7e6..b2c8a296d2 100644 --- a/nipype/testing/tests/test_utils.py +++ b/nipype/testing/tests/test_utils.py @@ -7,7 +7,8 @@ import os import warnings import subprocess -from mock import patch, MagicMock +from unittest.mock import patch, MagicMock +from unittest import SkipTest from nipype.testing.utils import TempFATFS @@ -15,10 +16,9 @@ def test_tempfatfs(): try: fatfs = TempFATFS() except (IOError, OSError): - warnings.warn("Cannot mount FAT filesystems with FUSE") - else: - with fatfs as tmp_dir: - assert os.path.exists(tmp_dir) + raise SkipTest("Cannot mount FAT filesystems with FUSE") + with fatfs as tmp_dir: + assert os.path.exists(tmp_dir) @patch( diff --git a/nipype/tests/test_nipype.py b/nipype/tests/test_nipype.py index 60fa92d141..bb80da601f 100644 --- a/nipype/tests/test_nipype.py +++ b/nipype/tests/test_nipype.py @@ -1,3 +1,4 @@ +import os from .. import get_info from ..info import get_nipype_gitversion import pytest @@ -46,26 +47,28 @@ def test_no_et(tmp_path): from nipype.interfaces import utility as niu from nipype.interfaces.base import BaseInterface + et = os.getenv("NIPYPE_NO_ET") is None + # Pytest doesn't trigger this, so let's pretend it's there with patch.object(BaseInterface, "_etelemetry_version_data", {}): # Direct function call - environment not set f = niu.Function(function=_check_no_et) res = f.run() - assert res.outputs.out is True + assert res.outputs.out == et # Basic node - environment not set n = pe.Node( niu.Function(function=_check_no_et), name="n", base_dir=str(tmp_path) ) res = n.run() - assert res.outputs.out is True + assert res.outputs.out == et # Linear run - environment not set wf1 = pe.Workflow(name="wf1", base_dir=str(tmp_path)) wf1.add_nodes([pe.Node(niu.Function(function=_check_no_et), name="n")]) res = wf1.run() - assert next(iter(res.nodes)).result.outputs.out is True + assert next(iter(res.nodes)).result.outputs.out == et # MultiProc run - environment initialized with NIPYPE_NO_ET wf2 = pe.Workflow(name="wf2", base_dir=str(tmp_path)) @@ -91,9 +94,9 @@ def test_no_et(tmp_path): ] ) res = wf4.run(plugin="MultiProc", plugin_args={"n_procs": 1}) - assert next(iter(res.nodes)).result.outputs.out is True + assert next(iter(res.nodes)).result.outputs.out == et - # LegacyMultiProc run - environment initialized with NIPYPE_NO_ET + # run_without_submitting - environment not set wf5 = pe.Workflow(name="wf5", base_dir=str(tmp_path)) wf5.add_nodes( [ @@ -105,4 +108,4 @@ def test_no_et(tmp_path): ] ) res = wf5.run(plugin="LegacyMultiProc", plugin_args={"n_procs": 1}) - assert next(iter(res.nodes)).result.outputs.out is True + assert next(iter(res.nodes)).result.outputs.out == et diff --git a/nipype/utils/tests/test_config.py b/nipype/utils/tests/test_config.py index 47dae20d88..5d9b5d57df 100644 --- a/nipype/utils/tests/test_config.py +++ b/nipype/utils/tests/test_config.py @@ -5,7 +5,7 @@ import sys import pytest from nipype import config -from mock import MagicMock +from unittest.mock import MagicMock try: import xvfbwrapper diff --git a/nipype/utils/tests/test_filemanip.py b/nipype/utils/tests/test_filemanip.py index e8e317935f..9c54ff02ee 100644 --- a/nipype/utils/tests/test_filemanip.py +++ b/nipype/utils/tests/test_filemanip.py @@ -3,10 +3,9 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: import os import time -import warnings from pathlib import Path -import mock +from unittest import mock, SkipTest import pytest from ...testing import TempFATFS from ...utils.filemanip import ( @@ -238,22 +237,22 @@ def test_copyfallback(_temp_analyze_files): try: fatfs = TempFATFS() except (IOError, OSError): - warnings.warn("Fuse mount failed. copyfile fallback tests skipped.") - else: - with fatfs as fatdir: - tgt_img = os.path.join(fatdir, imgname) - tgt_hdr = os.path.join(fatdir, hdrname) - for copy in (True, False): - for use_hardlink in (True, False): - copyfile(orig_img, tgt_img, copy=copy, use_hardlink=use_hardlink) - assert os.path.exists(tgt_img) - assert os.path.exists(tgt_hdr) - assert not os.path.islink(tgt_img) - assert not os.path.islink(tgt_hdr) - assert not os.path.samefile(orig_img, tgt_img) - assert not os.path.samefile(orig_hdr, tgt_hdr) - os.unlink(tgt_img) - os.unlink(tgt_hdr) + raise SkipTest("Fuse mount failed. copyfile fallback tests skipped.") + + with fatfs as fatdir: + tgt_img = os.path.join(fatdir, imgname) + tgt_hdr = os.path.join(fatdir, hdrname) + for copy in (True, False): + for use_hardlink in (True, False): + copyfile(orig_img, tgt_img, copy=copy, use_hardlink=use_hardlink) + assert os.path.exists(tgt_img) + assert os.path.exists(tgt_hdr) + assert not os.path.islink(tgt_img) + assert not os.path.islink(tgt_hdr) + assert not os.path.samefile(orig_img, tgt_img) + assert not os.path.samefile(orig_hdr, tgt_hdr) + os.unlink(tgt_img) + os.unlink(tgt_hdr) def test_get_related_files(_temp_analyze_files): @@ -295,7 +294,7 @@ def test_ensure_list(filename, expected): @pytest.mark.parametrize( - "list, expected", [(["foo.nii"], "foo.nii"), (["foo", "bar"], ["foo", "bar"]),] + "list, expected", [(["foo.nii"], "foo.nii"), (["foo", "bar"], ["foo", "bar"])] ) def test_simplify_list(list, expected): x = simplify_list(list) diff --git a/nipype/utils/tests/test_provenance.py b/nipype/utils/tests/test_provenance.py index 393a66b6b2..159a59ba7a 100644 --- a/nipype/utils/tests/test_provenance.py +++ b/nipype/utils/tests/test_provenance.py @@ -2,10 +2,19 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: import os +from nibabel.optpkg import optional_package +import pytest + +_, have_rdflib5, _ = optional_package("rdflib", min_version="5.0.0") from nipype.utils.provenance import ProvStore, safe_encode +needs_rdflib5 = pytest.mark.skipif( + not have_rdflib5, reason="Test requires rdflib 5.0.0 or higher" +) + +@needs_rdflib5 def test_provenance(tmpdir): from nipype.interfaces.base import CommandLine @@ -17,6 +26,7 @@ def test_provenance(tmpdir): assert "echo hello" in provn +@needs_rdflib5 def test_provenance_exists(tmpdir): tmpdir.chdir() from nipype import config diff --git a/tools/checkspecs.py b/tools/checkspecs.py index e06f862338..032fd122cc 100644 --- a/tools/checkspecs.py +++ b/tools/checkspecs.py @@ -8,8 +8,6 @@ import sys import warnings -from nipype.interfaces.base import BaseInterface - import black @@ -182,6 +180,8 @@ def test_specs(self, uri): Returns ------- """ + from nipype.interfaces.base import BaseInterface + # get the names of all classes and functions _, classes = self._parse_module(uri) if not classes: @@ -480,6 +480,7 @@ def check_modules(self): if __name__ == "__main__": + os.environ["NIPYPE_NO_ET"] = "1" package = "nipype" ic = InterfaceChecker(package) # Packages that should not be included in generated API docs. diff --git a/tools/update_zenodo.py b/tools/update_zenodo.py index cece8d53fd..e63c2ed2e7 100755 --- a/tools/update_zenodo.py +++ b/tools/update_zenodo.py @@ -14,6 +14,9 @@ def decommify(name): # These names should go last CREATORS_LAST = ["Krzysztof J. Gorgolewski", "Satrajit Ghosh"] +# Contributors that have requested not to be cited (or bothered) +BLACKLIST = {"Jonathan R. Williford"} + if __name__ == "__main__": git_root = Path(git.Repo(".", search_parent_directories=True).working_dir) zenodo_file = git_root / ".zenodo.json" @@ -44,7 +47,8 @@ def decommify(name): ) match, score = matches[0] if score <= 80: - print("No entry to sort:", committer) + if committer not in BLACKLIST: + print("No entry to sort:", committer) continue existing_creators.discard(match) committers.append(match)