├── .circleci ├── artifact_path ├── config.yml └── version.py ├── .codecov.yml ├── .coveragerc ├── .docker └── files │ ├── neurodebian.gpg │ └── nipype.cfg ├── .dockerignore ├── .git-blame-ignore-revs ├── .git_archival.txt ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build-test-publish.yml │ ├── docs-build-update.yml │ └── validate.yml ├── .gitignore ├── .mailmap ├── .maint ├── CONTRIBUTORS.md ├── FORMER.md ├── MAINTAINERS.md ├── PIs.md ├── ROADMAP.md └── update_authors.py ├── .pep8speaks.yml ├── .pre-commit-config.yaml ├── .zenodo.json ├── CHANGES.rst ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.rst ├── docs ├── Makefile ├── _static │ ├── config-example.toml │ ├── css │ │ └── version-switch.css │ ├── js │ │ └── version-switch.js │ └── sdcflows-OHBM21-fig1.png ├── _templates │ └── sidebar │ │ └── brand.html ├── api.rst ├── changes.rst ├── cli.rst ├── conf.py ├── examples.rst ├── index.rst ├── installation.rst ├── links.rst ├── methods.rst ├── notebooks │ ├── SDC - Theory and physics.ipynb │ └── fieldmap.nii.gz ├── reference │ └── .gitignore ├── requirements.txt └── tools │ ├── LICENSE.txt │ ├── apigen.py │ └── buildmodref.py ├── env.yml ├── pyproject.toml ├── requirements.txt ├── sdcflows ├── __init__.py ├── _warnings.py ├── cli │ ├── __init__.py │ ├── main.py │ ├── parser.py │ ├── tests │ │ ├── __init__.py │ │ └── test_find_estimators.py │ └── workflow.py ├── config.py ├── conftest.py ├── data │ ├── __init__.py │ ├── affine.json │ ├── flirtsch │ │ ├── b02b0.cnf │ │ ├── b02b0_1.cnf │ │ ├── b02b0_2.cnf │ │ ├── b02b0_4.cnf │ │ └── b02b0_quick.cnf │ ├── fmap-any_registration.json │ ├── fmap-any_registration_testing.json │ ├── fmap_atlas.nii.gz │ ├── fmap_atlas_2_MNI152NLin2009cAsym_affine.mat │ ├── sd_syn.json │ ├── sd_syn_sloppy.json │ └── translation_rigid.json ├── fieldmaps.py ├── interfaces │ ├── __init__.py │ ├── brainmask.py │ ├── bspline.py │ ├── epi.py │ ├── fmap.py │ ├── reportlets.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_bspline.py │ │ ├── test_epi.py │ │ ├── test_fmap.py │ │ ├── test_reportlets.py │ │ └── test_utils.py │ └── utils.py ├── tests │ ├── __init__.py │ ├── data │ │ ├── dsA │ │ │ ├── dataset_description.json │ │ │ └── sub-01 │ │ │ │ ├── anat │ │ │ │ ├── sub-01_FLAIR.nii.gz │ │ │ │ ├── sub-01_T1w.nii.gz │ │ │ │ └── sub-01_T2w.nii.gz │ │ │ │ ├── dwi │ │ │ │ ├── sub-01_dir-AP_dwi.nii.gz │ │ │ │ ├── sub-01_dir-AP_sbref.json │ │ │ │ ├── sub-01_dir-AP_sbref.nii.gz │ │ │ │ ├── sub-01_dir-LR_dwi.nii.gz │ │ │ │ ├── sub-01_dir-LR_sbref.json │ │ │ │ ├── sub-01_dir-LR_sbref.nii.gz │ │ │ │ ├── sub-01_dir-PA_dwi.nii.gz │ │ │ │ ├── sub-01_dir-PA_sbref.json │ │ │ │ ├── sub-01_dir-PA_sbref.nii.gz │ │ │ │ ├── sub-01_dir-RL_dwi.nii.gz │ │ │ │ ├── sub-01_dir-RL_sbref.json │ │ │ │ └── sub-01_dir-RL_sbref.nii.gz │ │ │ │ ├── fmap │ │ │ │ ├── sub-01_acq-single_dir-PA_epi.json │ │ │ │ ├── sub-01_acq-single_dir-PA_epi.nii.gz │ │ │ │ ├── sub-01_dir-AP_epi.json │ │ │ │ ├── sub-01_dir-AP_epi.nii.gz │ │ │ │ ├── sub-01_dir-LR_epi.json │ │ │ │ ├── sub-01_dir-LR_epi.nii.gz │ │ │ │ ├── sub-01_dir-PA_epi.json │ │ │ │ ├── sub-01_dir-PA_epi.nii.gz │ │ │ │ ├── sub-01_dir-RL_epi.json │ │ │ │ ├── sub-01_dir-RL_epi.nii.gz │ │ │ │ ├── sub-01_fieldmap.json │ │ │ │ ├── sub-01_fieldmap.nii.gz │ │ │ │ ├── sub-01_magnitude.nii.gz │ │ │ │ ├── sub-01_magnitude1.nii.gz │ │ │ │ ├── sub-01_magnitude2.nii.gz │ │ │ │ ├── sub-01_phase1.json │ │ │ │ ├── sub-01_phase1.nii.gz │ │ │ │ ├── sub-01_phase2.json │ │ │ │ ├── sub-01_phase2.nii.gz │ │ │ │ ├── sub-01_phasediff.json │ │ │ │ └── sub-01_phasediff.nii.gz │ │ │ │ └── func │ │ │ │ ├── sub-01_task-rest_bold.nii.gz │ │ │ │ └── sub-01_task-rest_sbref.nii.gz │ │ ├── dsB │ │ │ ├── dataset_description.json │ │ │ └── sub-01 │ │ │ │ └── ses-1 │ │ │ │ ├── anat │ │ │ │ ├── sub-01_ses-1_FLAIR.nii.gz │ │ │ │ ├── sub-01_ses-1_T1w.nii.gz │ │ │ │ └── sub-01_ses-1_T2w.nii.gz │ │ │ │ ├── dwi │ │ │ │ ├── sub-01_ses-1_dir-AP_dwi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-AP_sbref.json │ │ │ │ ├── sub-01_ses-1_dir-AP_sbref.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-LR_dwi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-LR_sbref.json │ │ │ │ ├── sub-01_ses-1_dir-LR_sbref.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-PA_dwi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-PA_sbref.json │ │ │ │ ├── sub-01_ses-1_dir-PA_sbref.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-RL_dwi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-RL_sbref.json │ │ │ │ └── sub-01_ses-1_dir-RL_sbref.nii.gz │ │ │ │ ├── fmap │ │ │ │ ├── sub-01_ses-1_acq-single_dir-PA_epi.json │ │ │ │ ├── sub-01_ses-1_acq-single_dir-PA_epi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-AP_epi.json │ │ │ │ ├── sub-01_ses-1_dir-AP_epi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-LR_epi.json │ │ │ │ ├── sub-01_ses-1_dir-LR_epi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-PA_epi.json │ │ │ │ ├── sub-01_ses-1_dir-PA_epi.nii.gz │ │ │ │ ├── sub-01_ses-1_dir-RL_epi.json │ │ │ │ ├── sub-01_ses-1_dir-RL_epi.nii.gz │ │ │ │ ├── sub-01_ses-1_fieldmap.json │ │ │ │ ├── sub-01_ses-1_fieldmap.nii.gz │ │ │ │ ├── sub-01_ses-1_magnitude.nii.gz │ │ │ │ ├── sub-01_ses-1_magnitude1.nii.gz │ │ │ │ ├── sub-01_ses-1_magnitude2.nii.gz │ │ │ │ ├── sub-01_ses-1_phase1.json │ │ │ │ ├── sub-01_ses-1_phase1.nii.gz │ │ │ │ ├── sub-01_ses-1_phase2.json │ │ │ │ ├── sub-01_ses-1_phase2.nii.gz │ │ │ │ ├── sub-01_ses-1_phasediff.json │ │ │ │ └── sub-01_ses-1_phasediff.nii.gz │ │ │ │ └── func │ │ │ │ ├── sub-01_ses-1_task-rest_bold.nii.gz │ │ │ │ └── sub-01_ses-1_task-rest_sbref.nii.gz │ │ ├── dsC │ │ │ ├── dataset_description.json │ │ │ ├── sub-01 │ │ │ │ ├── anat │ │ │ │ │ ├── sub-01_FLAIR.nii.gz │ │ │ │ │ ├── sub-01_T1w.nii.gz │ │ │ │ │ └── sub-01_T2w.nii.gz │ │ │ │ ├── dwi │ │ │ │ │ ├── sub-01_dir-AP_dwi.nii.gz │ │ │ │ │ ├── sub-01_dir-AP_sbref.json │ │ │ │ │ ├── sub-01_dir-AP_sbref.nii.gz │ │ │ │ │ ├── sub-01_dir-LR_dwi.nii.gz │ │ │ │ │ ├── sub-01_dir-LR_sbref.json │ │ │ │ │ ├── sub-01_dir-LR_sbref.nii.gz │ │ │ │ │ ├── sub-01_dir-PA_dwi.nii.gz │ │ │ │ │ ├── sub-01_dir-PA_sbref.json │ │ │ │ │ ├── sub-01_dir-PA_sbref.nii.gz │ │ │ │ │ ├── sub-01_dir-RL_dwi.nii.gz │ │ │ │ │ ├── sub-01_dir-RL_sbref.json │ │ │ │ │ └── sub-01_dir-RL_sbref.nii.gz │ │ │ │ ├── fmap │ │ │ │ │ ├── sub-01_acq-single_dir-PA_epi.json │ │ │ │ │ ├── sub-01_acq-single_dir-PA_epi.nii.gz │ │ │ │ │ ├── sub-01_dir-AP_epi.json │ │ │ │ │ ├── sub-01_dir-AP_epi.nii.gz │ │ │ │ │ ├── sub-01_dir-LR_epi.json │ │ │ │ │ ├── sub-01_dir-LR_epi.nii.gz │ │ │ │ │ ├── sub-01_dir-PA_epi.json │ │ │ │ │ ├── sub-01_dir-PA_epi.nii.gz │ │ │ │ │ ├── sub-01_dir-RL_epi.json │ │ │ │ │ ├── sub-01_dir-RL_epi.nii.gz │ │ │ │ │ ├── sub-01_fieldmap.json │ │ │ │ │ ├── sub-01_fieldmap.nii.gz │ │ │ │ │ ├── sub-01_magnitude.nii.gz │ │ │ │ │ ├── sub-01_magnitude1.nii.gz │ │ │ │ │ ├── sub-01_magnitude2.nii.gz │ │ │ │ │ ├── sub-01_phase1.json │ │ │ │ │ ├── sub-01_phase1.nii.gz │ │ │ │ │ ├── sub-01_phase2.json │ │ │ │ │ ├── sub-01_phase2.nii.gz │ │ │ │ │ ├── sub-01_phasediff.json │ │ │ │ │ └── sub-01_phasediff.nii.gz │ │ │ │ └── func │ │ │ │ │ ├── sub-01_task-rest_bold.nii.gz │ │ │ │ │ └── sub-01_task-rest_sbref.nii.gz │ │ │ └── task-rest_bold.json │ │ ├── epi.nii.gz │ │ ├── epi2fmap_xfm.txt │ │ ├── field-coeff-tests.nii.gz │ │ ├── fmap2epi_xfm.txt │ │ ├── topup-coeff-fixed.nii.gz │ │ ├── topup-coeff.nii.gz │ │ └── topup-field.nii.gz │ ├── test_fieldmaps.py │ ├── test_transform.py │ └── test_version.py ├── transform.py ├── utils │ ├── __init__.py │ ├── bimap.py │ ├── epimanip.py │ ├── misc.py │ ├── phasemanip.py │ ├── telemetry.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_misc.py │ │ ├── test_phasemanip.py │ │ ├── test_tools.py │ │ └── test_wrangler.py │ ├── tools.py │ └── wrangler.py ├── viz │ ├── __init__.py │ └── utils.py └── workflows │ ├── __init__.py │ ├── ancillary.py │ ├── apply │ ├── __init__.py │ ├── correction.py │ ├── registration.py │ └── tests │ │ ├── __init__.py │ │ ├── test_correction.py │ │ └── test_registration.py │ ├── base.py │ ├── fit │ ├── __init__.py │ ├── base.py │ ├── fieldmap.py │ ├── pepolar.py │ ├── syn.py │ └── tests │ │ ├── __init__.py │ │ ├── test_fit.py │ │ ├── test_pepolar.py │ │ ├── test_phdiff.py │ │ └── test_syn.py │ ├── outputs.py │ └── tests │ ├── __init__.py │ ├── test_ancillary.py │ ├── test_base.py │ └── test_integration.py ├── tools └── cache_templateflow.py └── tox.ini /.circleci/artifact_path: -------------------------------------------------------------------------------- 1 | /0/tmp/gh-pages/docs/_build/no_version_html/html/index.html 2 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | empty: 5 | machine: 6 | image: ubuntu-2204:current 7 | steps: 8 | - run: echo Not doing nothing. 9 | 10 | workflows: 11 | version: 2 12 | build_deploy: 13 | jobs: 14 | - empty 15 | -------------------------------------------------------------------------------- /.circleci/version.py: -------------------------------------------------------------------------------- 1 | """Get sdcflows version.""" 2 | 3 | import sdcflows 4 | 5 | print(sdcflows.__version__, end='', file=open('/tmp/.docker-version.txt', 'w')) 6 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "0...100" 3 | ignore: # files and folders that will be removed during processing 4 | - "sdcflows/_version.py" 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = 4 | */tests/* 5 | sdcflows/conftest.py 6 | sdcflows/_version.py 7 | 8 | [report] 9 | # Regexes for lines to exclude from consideration 10 | exclude_lines = 11 | raise NotImplementedError 12 | warnings\.warn 13 | -------------------------------------------------------------------------------- /.docker/files/neurodebian.gpg: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1 3 | 4 | mQGiBEQ7TOgRBADvaRsIZ3VZ6Qy7PlDpdMm97m0OfvouOj/HhjOM4M3ECbGn4cYh 5 | vN1gK586s3sUsUcNQ8LuWvNsYhxYsVTZymCReJMEDxod0U6/z/oIbpWv5svF3kpl 6 | ogA66Ju/6cZx62RiCSOkskI6A3Waj6xHyEo8AGOPfzbMoOOQ1TS1u9s2FwCgxziL 7 | wADvKYlDZnWM03QtqIJVD8UEAOks9Q2OqFoqKarj6xTRdOYIBVEp2jhozZUZmLmz 8 | pKL9E4NKGfixqxdVimFcRUGM5h7R2w7ORqXjCzpiPmgdv3jJLWDnmHLmMYRYQc8p 9 | 5nqo8mxuO3zJugxBemWoacBDd1MJaH7nK20Hsk9L/jvU/qLxPJotMStTnwO+EpsK 10 | HlihA/9ZpvzR1QWNUd9nSuNR3byJhaXvxqQltsM7tLqAT4qAOJIcMjxr+qESdEbx 11 | NHM5M1Y21ZynrsQw+Fb1WHXNbP79vzOxHoZR0+OXe8uUpkri2d9iOocre3NUdpOO 12 | JHtl6cGGTFILt8tSuOVxMT/+nlo038JQB2jARe4B85O0tkPIPbQybmV1cm8uZGVi 13 | aWFuLm5ldCBhcmNoaXZlIDxtaWNoYWVsLmhhbmtlQGdtYWlsLmNvbT6IRgQQEQgA 14 | BgUCTVHJKwAKCRCNEUVjdcAkyOvzAJ0abJz+f2a6VZG1c9T8NHMTYh1atwCgt0EE 15 | 3ZZd/2in64jSzu0miqhXbOKISgQQEQIACgUCSotRlwMFAXgACgkQ93+NsjFEvg8n 16 | JgCfWcdJbILBtpLZCocvOzlLPqJ0Fn0AoI4EpJRxoUnrtzBGUC1MqecU7WsDiGAE 17 | ExECACAFAkqLUWcCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCl0y8BJkml 18 | qVklAJ4h2V6MdQkSAThF5c2Gkq6eSoIQYQCeM0DWyB9Bl+tTPSTYXwwZi2uoif20 19 | QmFwc3kuZ3NlLnVuaS1tYWdkZWJ1cmcuZGUgRGViaWFuIEFyY2hpdmUgPG1pY2hh 20 | ZWwuaGFua2VAZ21haWwuY29tPohGBBARAgAGBQJEO03FAAoJEPd/jbIxRL4PU18A 21 | n3tn7i4qdlMi8kHbYWFoabsKc9beAJ9sl/leZNCYNMGhz+u6BQgyeLKw94heBBMR 22 | AgAeBQJEO0zoAhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEKXTLwEmSaWpVdoA 23 | n27DvtZizNEbhz3wRUPQMiQjtqdvAJ9rS9YdPe5h5o5gHx3mw3BSkOttdYheBBMR 24 | AgAeBQJEO0zoAhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEKXTLwEmSaWpVdoA 25 | oLhwWL+E+2I9lrUf4Lf26quOK9vLAKC9ZpIF2tUirFFkBWnQvu13/TA0SokCHAQQ 26 | AQIABgUCTSNBgQAKCRDAc9Iof/uem4NpEACQ8jxmaCaS/qk/Y4GiwLA5bvKosG3B 27 | iARZ2v5UWqCZQ1tS56yKse/lCIzXQqU9BnYW6wOI2rvFf9meLfd8h96peG6oKscs 28 | fbclLDIf68bBvGBQaD0VYFi/Fk/rxmTQBOCQ3AJZs8O5rIM4gPGE0QGvSZ1h7VRw 29 | 3Uyeg4jKXLIeJn2xEmOJgt3auAR2FyKbzHaX9JCoByJZ/eU23akNl9hgt7ePlpXo 30 | 74KNYC58auuMUhCq3BQDB+II4ERYMcmFp1N5ZG05Cl6jcaRRHDXz+Ax6DWprRI1+ 31 | RH/Yyae6LmKpeJNwd+vM14aawnNO9h8IAQ+aJ3oYZdRhGyybbin3giJ10hmWveg/ 32 | Pey91Nh9vBCHdDkdPU0s9zE7z/PHT0c5ccZRukxfZfkrlWQ5iqu3V064ku5f4PBy 33 | 8UPSkETcjYgDnrdnwqIAO+oVg/SFlfsOzftnwUrvwIcZlXAgtP6MEEAs/38e/JIN 34 | g4VrpdAy7HMGEUsh6Ah6lvGQr+zBnG44XwKfl7e0uCYkrAzUJRGM5vx9iXvFMcMu 35 | jv9EBNNBOU8/Y6MBDzGZhgaoeI27nrUvaveJXjAiDKAQWBLjtQjINZ8I9uaSGOul 36 | 8kpbFavE4eS3+KhISrSHe4DuAa3dk9zI+FiPvXY1ZyfQBtNpR+gYFY6VxMbHhY1U 37 | lSLHO2eUIQLdYbRITmV1cm9EZWJpYW4gQXJjaGl2ZSBLZXkgPHBrZy1leHBwc3kt 38 | bWFpbnRhaW5lcnNAbGlzdHMuYWxpb3RoLmRlYmlhbi5vcmc+iEYEEBEIAAYFAk1R 39 | yQYACgkQjRFFY3XAJMgEWwCggx4Gqlcrt76TSMlbU94cESo55AEAoJ3asQEMpe8t 40 | QUX+5aikw3z1AUoCiEoEEBECAAoFAkqf/3cDBQF4AAoJEPd/jbIxRL4PxyMAoKUI 41 | RPWlHCj/+HSFfwhos68wcSwmAKChuC00qutDro+AOo+uuq6YoHXj+ohgBBMRAgAg 42 | BQJKn/8bAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQpdMvASZJpalDggCe 43 | KF9KOgOPdQbFnKXl8KtHory4EEwAnA7jxgorE6kk2QHEXFSF8LzOOH4GiGMEExEC 44 | ACMCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCSp//RgIZAQAKCRCl0y8BJkml 45 | qekFAKCRyt4+FoCzmBbRUUP3Cr8PzH++IgCgkno4vdjsWdyAey8e0KpITTXMFrmJ 46 | AhwEEAECAAYFAk0jQYEACgkQwHPSKH/7npsFfw/+P8B8hpM3+T1fgboBa4R32deu 47 | n8m6b8vZMXwuo/awQtMpzjem8JGXSUQm8iiX4hDtjq6ZoPrlN8T4jNmviBt/F5jI 48 | Jji/PYmhq+Zn9s++mfx+aF4IJrcHJWFkg/6kJzn4oSdl/YlvKf4VRCcQNtj4xV87 49 | GsdamnzU17XapLVMbSaVKh+6Af7ZLDerEH+iAq733HsYaTK+1xKmN7EFVXgS7bZ1 50 | 9C4LTzc97bVHSywpT9yIrg9QQs/1kshfVIHDKyhjF6IwzSVbeGAIL3Oqo5zOMkWv 51 | 7JlEIkkhTyl+FETxNMTMYjAk+Uei3kRodneq3YBF2uFYSEzrXQgHAyn37geiaMYj 52 | h8wu6a85nG1NS0SdxiZDIePmbvD9vWxFZUWYJ/h9ifsLivWcVXlvHoQ0emd+n2ai 53 | FhAck2xsuyHgnGIZMHww5IkQdu/TMqvbcR6d8Xulh+C4Tq7ppy+oTLADSBKII++p 54 | JQioYydRD529EUJgVlhyH27X6YAk3FuRD3zYZRYS2QECiKXvS665o3JRJ0ZSqNgv 55 | YOom8M0zz6bI9grnUoivMI4o7ISpE4ZwffEd37HVzmraaUHDXRhkulFSf1ImtXoj 56 | V9nNSM5p/+9eP7OioTZhSote6Vj6Ja1SZeRkXZK7BwqPbdO0VsYOb7G//ZiOlqs+ 57 | paRr92G/pwBfj5Dq8EK5Ag0ERDtM9RAIAN0EJqBPvLN0tEin/y4Fe0R4n+E+zNXg 58 | bBsq4WidwyUFy3h/6u86FYvegXwUqVS2OsEs5MwPcCVJOfaEthF7I89QJnP9Nfx7 59 | V5I9yFB53o9ii38BN7X+9gSjpfwXOvf/wIDfggxX8/wRFel37GRB7TiiABRArBez 60 | s5x+zTXvT++WPhElySj0uY8bjVR6tso+d65K0UesvAa7PPWeRS+3nhqABSFLuTTT 61 | MMbnVXCGesBrYHlFVXClAYrSIOX8Ub/UnuEYs9+hIV7U4jKzRF9WJhIC1cXHPmOh 62 | vleAf/I9h/0KahD7HLYud40pNBo5tW8jSfp2/Q8TIE0xxshd51/xy4MAAwUH+wWn 63 | zsYVk981OKUEXul8JPyPxbw05fOd6gF4MJ3YodO+6dfoyIl3bewk+11KXZQALKaO 64 | 1xmkAEO1RqizPeetoadBVkQBp5xPudsVElUTOX0pTYhkUd3iBilsCYKK1/KQ9KzD 65 | I+O/lRsm6L9lc6rV0IgPU00P4BAwR+x8Rw7TJFbuS0miR3lP1NSguz+/kpjxzmGP 66 | LyHJ+LVDYFkk6t0jPXhqFdUY6McUTBDEvavTGlVO062l9APTmmSMVFDsPN/rBes2 67 | rYhuuT+lDp+gcaS1UoaYCIm9kKOteQBnowX9V74Z+HKEYLtwILaSnNe6/fNSTvyj 68 | g0z+R+sPCY4nHewbVC+ISQQYEQIACQUCRDtM9QIbDAAKCRCl0y8BJkmlqbecAJ9B 69 | UdSKVg9H+fQNyP5sbOjj4RDtdACfXHrRHa2+XjJP0dhpvJ8IfvYnQsU= 70 | =fAJZ 71 | -----END PGP PUBLIC KEY BLOCK----- 72 | -------------------------------------------------------------------------------- /.docker/files/nipype.cfg: -------------------------------------------------------------------------------- 1 | [execution] 2 | hash_method = content 3 | poll_sleep_duration = 0.01 4 | remove_unnecessary_outputs = true 5 | crashfile_format = txt 6 | profile_runtime = false 7 | use_relative_paths = false 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # python cache 2 | __pycache__/**/* 3 | __pycache__ 4 | *.pyc 5 | 6 | # python distribution 7 | build/**/* 8 | build 9 | dist/**/* 10 | dist 11 | sdcflows.egg-info/**/* 12 | sdcflows.egg-info 13 | .eggs/**/* 14 | .eggs 15 | 16 | # pip installs 17 | src/**/* 18 | src/ 19 | 20 | # other 21 | work/**/* 22 | work 23 | out/**/* 24 | out/ 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Thu May 15 09:29:29 2025 -0400 - markiewicz@stanford.edu - run: ruff format [ignore-rev] 2 | d97ae316c0bdf71084a0732760ceed5221033fc2 3 | # Thu May 15 09:26:57 2025 -0400 - markiewicz@stanford.edu - run: ruff check --fix [ignore-rev] 4 | cacd409cce71f2e530ad1bd1e8b79f397cab81d4 5 | # Thu May 15 09:22:00 2025 -0400 - markiewicz@stanford.edu - run: pre-commit run --all [ignore-rev] 6 | 2b40bee0d6b2c88628760a8b1513851a439411e8 7 | -------------------------------------------------------------------------------- /.git_archival.txt: -------------------------------------------------------------------------------- 1 | node: b4a01f6e2071a538896e228cba9a43c10b8256cb 2 | node-date: 2025-05-15T12:33:02-04:00 3 | describe-name: 2.13.0-13-gb4a01f6e20 4 | ref-names: HEAD -> main 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .git_archival.txt export-subst 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every week 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/docs-build-update.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build & update docs 3 | 4 | on: 5 | push: 6 | branches: [ 'doc/*', 'docs/*', master, main, "maint/*" ] 7 | tags: [ '*' ] 8 | pull_request: 9 | branches: [ master, main, 'maint/*' ] 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | if: ${{ github.event_name != 'pull_request' || !contains(github.event.head_commit.message, '[skip ci]') }} 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | ssh-key: "${{ secrets.NIPREPS_DEPLOY }}" 24 | fetch-depth: 0 25 | 26 | - name: Determine current branch/tag name & set-up git author 27 | run: | 28 | if [[ "$GITHUB_REF" == refs/tags/* ]]; then 29 | CURBRANCH=${GITHUB_REF##*/} 30 | elif [[ "$GITHUB_REF" == refs/pull/* ]]; then 31 | CURBRANCH=${GITHUB_REF%/*} 32 | CURBRANCH=${CURBRANCH##*/} 33 | elif [[ "$GITHUB_REF" == refs/heads/* ]]; then 34 | CURBRANCH=${GITHUB_REF##*/} 35 | fi 36 | 37 | # Remove forward slashes 38 | CURBRANCH=$( echo $CURBRANCH | sed 's+/+_+g' ) 39 | echo "Building branch/tag ${CURBRANCH:-}, from git ref <$GITHUB_REF>" 40 | echo "CURBRANCH=${CURBRANCH}" >> ${GITHUB_ENV} 41 | 42 | # Pacify git if we were to commit something 43 | git config user.email "nipreps@gmail.com" 44 | git config user.name "NiPreps Bot" 45 | 46 | - name: Install GraphViz & pandoc 47 | run: | 48 | sudo apt-get update -y 49 | sudo apt-get install -y --no-install-recommends graphviz pandoc texlive 50 | 51 | - name: Set up Python 3 52 | uses: actions/setup-python@v5 53 | with: 54 | python-version: 3 55 | 56 | - name: Install dependencies 57 | run: | 58 | python -m pip install -U build hatch hatchling pip docutils 59 | python -m pip install -r docs/requirements.txt 60 | python -m hatch version | tail -n1 | xargs 61 | 62 | - name: Pre-install template 63 | shell: python 64 | run: | 65 | from templateflow.api import get 66 | get('MNI152NLin2009cAsym', desc='brain', resolution=1, suffix='T1w') 67 | get('MNI152NLin2009cAsym', desc='fMRIPrep', resolution=2, suffix='boldref') 68 | get('MNI152NLin2009cAsym', desc='brain', resolution=2, suffix='mask') 69 | get('MNI152NLin2009cAsym', label='brain', resolution=1, suffix='probseg') 70 | 71 | - name: Build docs 72 | run: | 73 | make -C docs/ SPHINXOPTS="-W" BUILDDIR="$HOME/docs" OUTDIR="${CURBRANCH:-html}" html 74 | 75 | - name: Push created tag to gh-pages 76 | if: startsWith(github.ref, 'refs/tags/') 77 | run: | 78 | MAJOR_MINOR=${CURBRANCH%.*} 79 | if [[ "${MAJOR_MINOR}" == "" ]]; then 80 | echo "Could not identify release series" 81 | exit 1 82 | fi 83 | git checkout -b gh-pages origin/gh-pages 84 | git rm -r ${MAJOR_MINOR}/ || true 85 | # It is fundamental that the directory does not exist at all. 86 | rm -rf ${MAJOR_MINOR} 87 | cp -r $HOME/docs/$CURBRANCH $PWD/${MAJOR_MINOR} 88 | git add ${MAJOR_MINOR} 89 | python -c "from pathlib import Path; import json; f=Path('versions.json'); d=json.loads(f.read_text()); d['tags'].append(\"${MAJOR_MINOR}\"); d['tags'] = list(sorted(set(d['tags']))); f.write_text(json.dumps(d, indent=4)); print('Updated versions.json')" 90 | git add versions.json 91 | git commit -m "rel(${CURBRANCH}): Update docs of ${MAJOR_MINOR} series" || true 92 | git push 93 | 94 | - name: Push "master" docs to gh-pages after a push to master (typically, a PR merge). 95 | if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' 96 | run: | 97 | if [[ "${CURBRANCH}" != "master" && "${CURBRANCH}" != "main" ]]; then 98 | echo "$CURBRANCH is not the default development branch" 99 | exit 1 100 | fi 101 | git checkout -b gh-pages origin/gh-pages 102 | git rm -r ${CURBRANCH}/ || true 103 | # It is fundamental that the directory does not exist at all. 104 | rm -rf ${CURBRANCH} 105 | cp -r $HOME/docs/$CURBRANCH $PWD/${CURBRANCH} 106 | git add ${CURBRANCH} 107 | git commit -am "docs(${CURBRANCH}): Update docs of development line" || true 108 | git push 109 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validations 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ master, main, 'maint/*' ] 8 | 9 | env: 10 | FORCE_COLOR: true 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | style: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: pipx run ruff check sdcflows/ 25 | - run: pipx run ruff format --diff sdcflows/ 26 | 27 | # codespell: 28 | # runs-on: ubuntu-latest 29 | # steps: 30 | # - uses: actions/checkout@v4 31 | # - uses: codespell-project/actions-codespell@v2 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # setuptools-scm 2 | _version.py 3 | 4 | # Byte-compiled / optimized / DLL files 5 | pip-wheel-metadata 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | docs/_build 31 | docs/api 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | 111 | # Mac OSX 112 | .DS_Store 113 | 114 | .*.swp 115 | line-contributors.txt 116 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Adriana Rivera-Dompenciel 2 | Adriana Rivera-Dompenciel <34017586+ariveradompenciel@users.noreply.github.com> 3 | Alejandro de la Vega 4 | Alejandro de la Vega delavega4 5 | Anibal Sólon Heinsfeld 6 | Asier Erramuzpe 7 | Basile Pinsard 8 | Basile Pinsard basile 9 | Basile Pinsard 10 | Blaise Frederick 11 | Blaise Frederick bbfrederick 12 | Christopher J. Markiewicz 13 | Christopher J. Markiewicz 14 | Christopher J. Markiewicz 15 | Daniel J. Lurie 16 | Eilidh MacNicol 17 | Elizabeth DuPre 18 | Franklin Feingold 19 | Franklin Feingold <35307458+franklin-feingold@users.noreply.github.com> 20 | Franz Liem 21 | Gabriel A. Devenyi 22 | Ilkay Isik 23 | James D. Kent 24 | James D. Kent 25 | James D. Kent Fred Mertz 26 | Jarod Roland 27 | Joseph B. Wexler 28 | Joseph B. Wexler Joe B. Wexler 29 | Joseph B. Wexler jbwexler 30 | Karolina Finc 31 | Karolina Finc kfinc 32 | Kevin Sitek 33 | Kevin Sitek sitek 34 | Krzysztof J. Gorgolewski 35 | Krzysztof J. Gorgolewski 36 | Krzysztof J. Gorgolewski 37 | Krzysztof J. Gorgolewski 38 | Krzysztof J. Gorgolewski 39 | Krzysztof J. Gorgolewski 40 | Krzysztof J. Gorgolewski 41 | Lea Waller 42 | Marc Bue <44844855+marcbue@users.noreply.github.com> 43 | Markus H. Sneve <44242647+markushs@users.noreply.github.com> 44 | Mathias Goncalves 45 | Mathias Goncalves 46 | Mathias Goncalves 47 | Mathieu Dugré <16450132+mathdugre@users.noreply.github.com> 48 | Matteo Visconti di Oleggio Castello 49 | Marc Bue <44844855+marcbue@users.noreply.github.com> 50 | Marc Bue <44844855+marcbue@users.noreply.github.com> marcbue <44844855+marcbue@users.noreply.github.com> 51 | Mikael Naveau 52 | Milton Camacho <70858549+miltoncamacho@users.noreply.github.com> 53 | Nir Jacoby 54 | Nir Jacoby 55 | Noah C. Benson 56 | Noah C. Benson noahbenson 57 | Oscar Esteban 58 | Oscar Esteban 59 | Pablo Velasco 60 | Rastko Ciric 61 | Rastko Ciric 62 | Rastko Ciric 63 | Romain Valabregue 64 | Ross Blair 65 | Ross Blair 66 | Saren Seeley <31291534+sarenseeley@users.noreply.github.com> 67 | Sebastian Urchs 68 | Sebastien Naze 69 | Shoshana Berleant 70 | Soichi Hayashi 71 | William Hedley Thompson 72 | William Hedley Thompson 73 | Yasser Aleman 74 | -------------------------------------------------------------------------------- /.maint/CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTORS 2 | 3 | This document lists those who have made contributions to the Project. 4 | As per the contributor guidelines, they should be included by default in any publications derived from the Project. 5 | 6 | If you are new to the project, don't forget to add your name and affiliation to the list of contributors here! Our Welcome Bot will send an automated message reminding this to first-time contributors. 7 | Before every release, unlisted contributors will be invited again to add their names to this file (just in case they missed the automated message from our Welcome Bot). 8 | 9 | | **Lastname** | **Name** | **Handle** | **ORCID** | **Affiliation** | 10 | | --- | --- | --- | --- | --- | 11 | | Adebimpe | Azeez | @a3sha2 | 0000-0001-9049-0135 | Perelman School of Medicine, University of Pennsylvania, PA, USA | 12 | | Aleman | Yasser | @yasseraleman | | | 13 | | Blair | Ross W. | @rwblair | 0000-0003-3007-1056 | Department of Psychology, Stanford University, CA, USA | 14 | | Camacho | Milton | @miltoncamacho | | | 15 | | Cieslak | Matthew | @mattcieslak | 0000-0002-1931-4734 | Perelman School of Medicine, University of Pennsylvania, PA, USA | 16 | | Dugré | Mathieu | @mathdugre | 0000-0003-2828-6031 | Concordia University | 17 | | Legarreta Gorroño | Jon Haitz | @jhlegarreta | 0000-0002-9661-1396 | Brigham and Women's Hospital, Mass General Brigham, Harvard Medical School, MA, USA | 18 | | MacNicol | Eilidh | @eilidhmacnicol | 0000-0003-3715-7012 | Department of Neuroimaging, King's College London | 19 | | Marabotto | Julien | @jmarabotto | 0009-0003-7070-5217 | Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland | 20 | | Meisler | Steven | @smeisler | 0000-0002-8888-1572 | Speech & Hearing Bioscience & Technology Program, Harvard University | 21 | | Naveau | Mikaël | @naveau | 0000-0001-6948-9068 | Cyceron, UMS 3408 (CNRS - UCBN), France | 22 | | Pinsard | Basile | @bpinsard | 0000-0002-4391-3075 | University of Montréal, Montréal, Canada | 23 | | Sadil | Patrick | @psadil | 0000-0003-4141-1343 | Johns Hopkins Bloomberg School of Public Health | 24 | | Salo | Taylor | @tsalo | 0000-0001-9813-3167 | Perelman School of Medicine, University of Pennsylvania, PA, USA | 25 | | Sitek | Kevin R. | @sitek | 0000-0002-2172-5786 | Speech & Hearing Bioscience & Technology Program, Harvard University | 26 | | Sneve | Markus H. | @markushs | 0000-0001-7644-7915 | Center for Lifespan Changes in Brain and Cognition, University of Oslo | 27 | | Waller | Lea | @hippocampusgirl | 0000-0002-3239-6957 | Charite Universitatsmedizin Berlin, Germany | 28 | | Zwiers | Marcel | @marcelzwiers | | | 29 | -------------------------------------------------------------------------------- /.maint/FORMER.md: -------------------------------------------------------------------------------- 1 | # FORMER MEMBERS 2 | 3 | This document lists former contributors or maintainers who want to disengage from the Project, and seek to be dismissed in communications or future papers. 4 | By adding your name to this list you are giving up on all your responsibilities to the project. 5 | Should you desire to be considered back as a contributor or maintainer, please remove your name from this list and proceed as prescribed in the governance documents. 6 | 7 | | **Lastname** | **Name** | **Handle** | 8 | | --- | --- | --- | 9 | | Berleant | Shoshana | @berleant | 10 | | Bot | NiPreps | @nipreps-bot | 11 | | Bot | Dependabot | @dependabot[bot] | 12 | | Gorgolewski | Krzysztof J. | @chrisgorgo | 13 | -------------------------------------------------------------------------------- /.maint/MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | This document lists the Maintainers of the project. 4 | Maintainers may be added once approved by the existing maintainers as described in the `../GOVERNANCE.md` document. 5 | By adding your name to this list you are agreeing to abide by the governance documents and to abide by all of the Organization's polices, including the code of conduct, trademark policy, and antitrust policy. 6 | If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies. 7 | 8 | 9 | 10 | | **Lastname** | **Name** | **Handle** | **ORCID** | **Affiliation** | 11 | | --- | --- | --- | --- | --- | 12 | | Esteban | Oscar | @oesteban | 0000-0001-8435-6191 | Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland | 13 | | Goncalves | Mathias | @mgxd | 0000-0002-7252-7771 | Department of Psychology, Stanford University, CA, USA | 14 | | Markiewicz | Christopher J. | @effigies | 0000-0002-6533-164X | Department of Psychology, Stanford University, CA, USA | 15 | -------------------------------------------------------------------------------- /.maint/PIs.md: -------------------------------------------------------------------------------- 1 | # PRINCIPAL INVESTIGATORS 2 | 3 | This documents the key personnel who oversees the development of the Project and secures funding. 4 | The names in this file are designated by the Organization's TSC at the time of accepting the Project under the Organization. 5 | Changes to this file must be approved by the TSC. 6 | When a PI ceases serving as such, by default, their name will be placed in the `CONTRIBUTORS.md` file should it not be there already. 7 | 8 | By having your name in this list you are agreeing to abide by the governance documents and to abide by all of the Organization's polices, including the code of conduct, trademark policy, and antitrust policy. 9 | If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies. 10 | 11 | | **Lastname** | **Name** | **Handle** | **ORCID** | **Affiliation** | 12 | | --- | --- | --- | --- | --- | --- | 13 | | Esteban | Oscar | @oesteban | 0000-0001-8435-6191 | Department of Radiology, Lausanne University Hospital and University of Lausanne | 14 | | Poldrack | Russell A. | @poldrack | 0000-0001-6755-0259 | Department of Psychology, Stanford University, CA, USA | 15 | | Satterthwaite | Theodore D. | | 0000-0001-7072-9399 | Perelman School of Medicine, University of Pennsylvania, PA, USA | 16 | -------------------------------------------------------------------------------- /.maint/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # ROADMAP 2 | 3 | This document states how the roadmap is built, discussed and monitored by the Maintainers, with the engagement of Contributors and PIs. 4 | 5 | For example, if the GitHub Projects feature is used, this document will contain a link to the corresponding Project and document who is responsible of managing the project, contacting Contributors and Maintainers to monitor the progress of activities, organizing meetings and discussions around the activities, etc. 6 | 7 | This document may also indicate how the *milestones* feature of the project is used and, as for Projects, who is responsible of what coordination actions, their frequence, etc. 8 | -------------------------------------------------------------------------------- /.pep8speaks.yml: -------------------------------------------------------------------------------- 1 | scanner: 2 | diff_only: True # Only show errors caused by the patch 3 | linter: flake8 4 | 5 | message: # Customize the comment made by the bot 6 | opened: # Messages when a new PR is submitted 7 | header: "Hello @{name}, thank you for submitting the Pull Request!" 8 | footer: "To test for issues locally, `pip install flake8` and then run `flake8 sdcflows`." 9 | updated: # Messages when new commits are added to the PR 10 | header: "Hello @{name}, Thank you for updating!" 11 | footer: "To test for issues locally, `pip install flake8` and then run `flake8 sdcflows`." 12 | no_errors: "Cheers! There are no style issues detected in this Pull Request. :beers: " 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: ".*/data/.*" 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-merge-conflict 8 | - id: check-json 9 | - id: check-toml 10 | - id: check-yaml 11 | - id: debug-statements 12 | - id: end-of-file-fixer 13 | - id: fix-byte-order-marker 14 | - id: trailing-whitespace 15 | # Enable when no significant PRs are in progress 16 | # - repo: https://github.com/psf/black 17 | # rev: 23.1.0 18 | # hooks: 19 | # - id: black 20 | # - repo: https://github.com/pycqa/isort 21 | # rev: 5.12.0 22 | # hooks: 23 | # - id: isort 24 | -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "SDCflows: Susceptibility Distortion Correction workFLOWS.", 3 | "description": "

Nipype-based neuroimaging workflows for susceptibility distortion correction (SDC) of echo-planar MRI images.

", 4 | "creators": [ 5 | { 6 | "orcid": "0000-0001-8435-6191", 7 | "affiliation": "Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland", 8 | "name": "Oscar Esteban" 9 | }, 10 | { 11 | "orcid": "0000-0002-6533-164X", 12 | "affiliation": "Department of Psychology, Stanford University, CA, USA", 13 | "name": "Christopher J. Markiewicz" 14 | }, 15 | { 16 | "orcid": "0000-0002-7252-7771", 17 | "affiliation": "Department of Psychology, Stanford University, CA, USA", 18 | "name": "Mathias Goncalves" 19 | } 20 | ], 21 | "contributors": [ 22 | { 23 | "orcid": "0009-0003-7070-5217", 24 | "affiliation": "Lausanne University Hospital and University of Lausanne, Lausanne, Switzerland", 25 | "name": "Julien Marabotto", 26 | "type": "Researcher" 27 | }, 28 | { 29 | "orcid": "0000-0002-4391-3075", 30 | "affiliation": "University of Montréal, Montréal, Canada", 31 | "name": "Basile Pinsard", 32 | "type": "Researcher" 33 | }, 34 | { 35 | "orcid": "0000-0001-9813-3167", 36 | "affiliation": "Perelman School of Medicine, University of Pennsylvania, PA, USA", 37 | "name": "Taylor Salo", 38 | "type": "Researcher" 39 | }, 40 | { 41 | "orcid": "0000-0003-4141-1343", 42 | "affiliation": "Johns Hopkins Bloomberg School of Public Health", 43 | "name": "Patrick Sadil", 44 | "type": "Researcher" 45 | }, 46 | { 47 | "orcid": "0000-0003-2828-6031", 48 | "affiliation": "Concordia University", 49 | "name": "Mathieu Dugré", 50 | "type": "Researcher" 51 | }, 52 | { 53 | "orcid": "0000-0003-3715-7012", 54 | "affiliation": "Department of Neuroimaging, King's College London", 55 | "name": "Eilidh MacNicol", 56 | "type": "Researcher" 57 | }, 58 | { 59 | "name": "Marcel Zwiers", 60 | "type": "Researcher", 61 | "affiliation": "Unknown affiliation" 62 | }, 63 | { 64 | "name": "Yasser Aleman", 65 | "type": "Researcher", 66 | "affiliation": "Unknown affiliation" 67 | }, 68 | { 69 | "orcid": "0000-0002-8888-1572", 70 | "affiliation": "Speech & Hearing Bioscience & Technology Program, Harvard University", 71 | "name": "Steven Meisler", 72 | "type": "Researcher" 73 | }, 74 | { 75 | "orcid": "0000-0002-3239-6957", 76 | "affiliation": "Charite Universitatsmedizin Berlin, Germany", 77 | "name": "Lea Waller", 78 | "type": "Researcher" 79 | }, 80 | { 81 | "name": "Milton Camacho", 82 | "type": "Researcher", 83 | "affiliation": "Unknown affiliation" 84 | }, 85 | { 86 | "orcid": "0000-0002-9661-1396", 87 | "affiliation": "Brigham and Women's Hospital, Mass General Brigham, Harvard Medical School, MA, USA", 88 | "name": "Jon Haitz Legarreta Gorroño", 89 | "type": "Researcher" 90 | }, 91 | { 92 | "orcid": "0000-0001-9049-0135", 93 | "affiliation": "Perelman School of Medicine, University of Pennsylvania, PA, USA", 94 | "name": "Azeez Adebimpe", 95 | "type": "Researcher" 96 | }, 97 | { 98 | "orcid": "0000-0002-2172-5786", 99 | "affiliation": "Speech & Hearing Bioscience & Technology Program, Harvard University", 100 | "name": "Kevin R. Sitek", 101 | "type": "Researcher" 102 | }, 103 | { 104 | "orcid": "0000-0001-7644-7915", 105 | "affiliation": "Center for Lifespan Changes in Brain and Cognition, University of Oslo", 106 | "name": "Markus H. Sneve", 107 | "type": "Researcher" 108 | }, 109 | { 110 | "orcid": "0000-0002-1931-4734", 111 | "affiliation": "Perelman School of Medicine, University of Pennsylvania, PA, USA", 112 | "name": "Matthew Cieslak", 113 | "type": "Researcher" 114 | }, 115 | { 116 | "orcid": "0000-0001-6948-9068", 117 | "affiliation": "Cyceron, UMS 3408 (CNRS - UCBN), France", 118 | "name": "Mikaël Naveau", 119 | "type": "Researcher" 120 | }, 121 | { 122 | "orcid": "0000-0003-3007-1056", 123 | "affiliation": "Department of Psychology, Stanford University, CA, USA", 124 | "name": "Ross W. Blair", 125 | "type": "Researcher" 126 | }, 127 | { 128 | "orcid": "0000-0001-7072-9399", 129 | "affiliation": "Perelman School of Medicine, University of Pennsylvania, PA, USA", 130 | "name": "Theodore D. Satterthwaite", 131 | "type": "Researcher" 132 | }, 133 | { 134 | "orcid": "0000-0001-6755-0259", 135 | "affiliation": "Department of Psychology, Stanford University, CA, USA", 136 | "name": "Russell A. Poldrack", 137 | "type": "Researcher" 138 | } 139 | ], 140 | "keywords": [ 141 | "neuroimaging", 142 | "workflow", 143 | "pipeline", 144 | "preprocessing", 145 | "MRI", 146 | "BIDS" 147 | ], 148 | "license": "apache-2.0", 149 | "related_identifiers": [ 150 | { 151 | "identifier": "https://fmriprep.org", 152 | "relation": "isPartOf", 153 | "scheme": "url" 154 | }, 155 | { 156 | "identifier": "10.1038/s41592-018-0235-4", 157 | "relation": "isPartOf", 158 | "scheme": "doi" 159 | } 160 | ], 161 | "upload_type": "software" 162 | } 163 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please check the [contributing guidelines of the NiPreps organization](https://www.nipreps.org/community/CONTRIBUTING) 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help docker 2 | .DEFAULT: help 3 | 4 | tag="latest" 5 | 6 | help: 7 | @echo "Premade recipes" 8 | @echo 9 | @echo "make docker [tag=TAG]" 10 | @echo "\tBuilds a docker image from source. Defaults to 'latest' tag." 11 | 12 | 13 | docker: 14 | docker build --rm -t nipreps/sdcflows:$(tag) \ 15 | --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ 16 | --build-arg VCS_REF=`git rev-parse --short HEAD` \ 17 | --build-arg VERSION=`python setup.py --version` . 18 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | SDCFlows 2 | -------- 3 | .. image:: https://img.shields.io/pypi/v/sdcflows.svg 4 | :target: https://pypi.python.org/pypi/sdcflows/ 5 | :alt: Latest Version 6 | 7 | .. image:: https://codecov.io/gh/nipreps/sdcflows/branch/master/graph/badge.svg?token=V2CS5adHYk 8 | :target: https://codecov.io/gh/nipreps/sdcflows 9 | 10 | .. image:: https://circleci.com/gh/nipreps/sdcflows.svg?style=svg 11 | :target: https://circleci.com/gh/nipreps/sdcflows 12 | 13 | .. image:: https://github.com/nipreps/sdcflows/workflows/Deps%20&%20CI/badge.svg 14 | :target: https://github.com/nipreps/sdcflows/actions 15 | 16 | SDCFlows (*Susceptibility Distortion Correction workFlows*) is a Python library of 17 | *NiPype*-based workflows to preprocess *B0* mapping data, estimate the corresponding 18 | fieldmap and finally correct for susceptibility distortions. 19 | Susceptibility-derived distortions are typically displayed by images acquired with EPI 20 | (echo-planar imaging) MR schemes. 21 | 22 | The library is designed to provide an easily accessible, state-of-the-art interface that is 23 | robust to differences in scan acquisition protocols and that requires minimal user input. 24 | 25 | This open-source neuroimaging data processing tool is being developed as a part of 26 | the MRI image analysis and reproducibility platform offered by 27 | `NiPreps `__. 28 | -------------------------------------------------------------------------------- /docs/_static/config-example.toml: -------------------------------------------------------------------------------- 1 | [environment] 2 | cpu_count = 36 3 | exec_env = "posix" 4 | free_mem = 13.7 5 | overcommit_policy = "heuristic" 6 | overcommit_limit = "50%" 7 | nipype_version = "1.8.7.dev0" 8 | templateflow_version = "23.0.0" 9 | total_memory = 62.51573181152344 10 | version = "2.6.1.dev5+gaa86b0b08f.d20231128" 11 | 12 | [execution] 13 | ants_float = false 14 | bids_dir = "/data/datasets/hcph" 15 | bids_database_dir = "/data/datasets/hcph/.bids-index" 16 | bids_database_wipe = false 17 | cwd = "/home/oesteban/tmp/sdcflows-hcph" 18 | debug = false 19 | dry_run = false 20 | layout = "BIDS Layout: .../data/datasets/hcph | Subjects: 1 | Sessions: 59 | Runs: 4" 21 | log_dir = "/home/oesteban/tmp/sdcflows-hcph/out/logs" 22 | log_level = 15 23 | notrack = false 24 | output_dir = "/home/oesteban/tmp/sdcflows-hcph/out" 25 | participant_label = [ "001",] 26 | pdb = false 27 | run_uuid = "20231129-112254_e9d6d38e-35ff-463e-9f7e-1ff15ecd54ac" 28 | templateflow_home = "/data/templateflow" 29 | work_dir = "/home/oesteban/tmp/sdcflows-hcph/work" 30 | write_graph = false 31 | 32 | [workflow] 33 | analysis_level = [ "participant",] 34 | fmapless = false 35 | species = "human" 36 | template_id = "MNI152NLin2009cAsym" 37 | 38 | [nipype] 39 | crashfile_format = "txt" 40 | get_linked_libs = false 41 | local_hash_check = true 42 | nprocs = 8 43 | omp_nthreads = 16 44 | plugin = "MultiProc" 45 | remove_node_directories = false 46 | resource_monitor = false 47 | stop_on_first_crash = true 48 | 49 | [execution.bids_filters] 50 | 51 | [nipype.plugin_args] 52 | maxtasksperchild = 1 53 | raise_insufficient = false 54 | -------------------------------------------------------------------------------- /docs/_static/css/version-switch.css: -------------------------------------------------------------------------------- 1 | .version-tree ul { 2 | list-style-type: none; 3 | } 4 | -------------------------------------------------------------------------------- /docs/_static/js/version-switch.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | var pathname = window.location.pathname; 3 | var cur_ver = $("#version-slug").text().replace(/^[\n\s]+|[\n\s]+$/g, '') 4 | var major_minor = "master"; 5 | if ( cur_ver.lastIndexOf(" (dev)") == -1 ) { 6 | major_minor = `${cur_ver.split('.')[0]}.${cur_ver.split('.')[1]}` 7 | } 8 | var relpath = pathname.substring(pathname.lastIndexOf(major_minor)).replace(/\/$/, ''); 9 | var levels = relpath.split("/").length - 1 10 | if ( levels == 0 ) { 11 | levels = 1 12 | relpath += "/" 13 | } 14 | var versions_file = "../".repeat(levels) + "versions.json" 15 | relpath = "../".repeat(levels) + relpath 16 | relpath = relpath.replace("//", "/") 17 | console.log(`relpath="${relpath}", cur_ver="${cur_ver}"`) 18 | 19 | $.getJSON(versions_file, function (data) { 20 | $("#version-slug").remove(); // Unnecessary if JSON was downloaded 21 | 22 | $.each(data["tags"].reverse(), function( i, val ) { 23 | var new_path = relpath.replace(major_minor, val) 24 | var item = `
  • ${val}
  • ` 25 | if ( i == 0 ) { 26 | item = `
  • ${val} (Latest Release)
  • ` 27 | } 28 | $("#v-tags").append(item) 29 | }); 30 | $.each(data["heads"].reverse(), function( i, val ) { 31 | var new_path = relpath.replace(major_minor, val) 32 | var item = `
  • ${val}
  • ` 33 | if ( val == "master" ) { 34 | item = `
  • ${val} (Development)
  • ` 35 | } 36 | $("#v-tags").append(item) 37 | }); 38 | }).fail(function() { 39 | $("#version-menu").hide(); // JSON download failed - hide dropdown 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /docs/_static/sdcflows-OHBM21-fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/docs/_static/sdcflows-OHBM21-fig1.png -------------------------------------------------------------------------------- /docs/_templates/sidebar/brand.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |
    8 |
      9 |
    • 10 | {{ version if version == release else version + ' (dev)' }} 11 | 12 | 13 |
        14 |
      15 |
      16 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Library API (application program interface) 2 | =========================================== 3 | 4 | Information on specific functions, classes, and methods. 5 | 6 | .. toctree:: 7 | :glob: 8 | 9 | api/sdcflows.cli 10 | api/sdcflows.config 11 | api/sdcflows.data 12 | api/sdcflows.fieldmaps 13 | api/sdcflows.interfaces 14 | api/sdcflows.transform 15 | api/sdcflows.utils 16 | api/sdcflows.viz 17 | api/sdcflows.workflows 18 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | ----------- 2 | What's new? 3 | ----------- 4 | 5 | .. include:: ../CHANGES.rst 6 | -------------------------------------------------------------------------------- /docs/cli.rst: -------------------------------------------------------------------------------- 1 | Standalone command line usage 2 | ============================= 3 | *SDCFlows* can execute fieldmap estimation from a BIDS-compliant dataset by using the *standalone command line interface*: 4 | 5 | .. argparse:: 6 | :module: sdcflows.cli.parser 7 | :func: _parser 8 | :prog: sdcflows 9 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | notebooks/SDC - Theory and physics 9 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: links.rst 2 | .. include:: ../README.rst 3 | 4 | Contents 5 | -------- 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | installation 10 | examples 11 | methods 12 | cli 13 | api 14 | changes 15 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: links.rst 3 | 4 | Installation 5 | ============ 6 | 7 | Make sure all of *SDCflows*' `External Dependencies`_ are installed. 8 | These tools must be installed and their binaries available in the 9 | system's ``$PATH``. 10 | A relatively interpretable description of how your environment can be set-up 11 | is found in the `Dockerfile `_. 12 | As an additional installation setting, FreeSurfer_ requires a license file. 13 | 14 | On a functional Python 3.5 (or above) environment with ``pip`` installed, 15 | *SDCflows* can be installed using the habitual command :: 16 | 17 | $ python -m pip install sdcflows 18 | 19 | Check your installation with the following command line :: 20 | 21 | $ python -c "from sdcflows import __version__; print(__version__)" 22 | 23 | 24 | External Dependencies 25 | --------------------- 26 | 27 | The *SDCflows* are written using Python 3.5 (or above), and are based on 28 | nipype_. 29 | 30 | *SDCflows* require some other neuroimaging software tools that are 31 | not handled by the Python's packaging system (Pypi) used to deploy 32 | the ``sdcflows`` package: 33 | 34 | - FSL_ (version 5.0.9) 35 | - ANTs_ (version 2.2.0 - NeuroDocker build) 36 | - AFNI_ (version Debian-16.2.07) 37 | - FreeSurfer_ (version 6.0.1) 38 | -------------------------------------------------------------------------------- /docs/links.rst: -------------------------------------------------------------------------------- 1 | .. _Nipype: http://nipype.readthedocs.io/en/latest/ 2 | .. _BIDS: http://bids.neuroimaging.io 3 | .. _Installation: installation.html 4 | .. _workflows: workflows.html 5 | .. _FSL: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/ 6 | .. _ANTs: https://stnava.github.io/ANTs/ 7 | .. _AFNI: https://afni.nimh.nih.gov/ 8 | .. _FreeSurfer: https://surfer.nmr.mgh.harvard.edu/ 9 | -------------------------------------------------------------------------------- /docs/notebooks/fieldmap.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/docs/notebooks/fieldmap.nii.gz -------------------------------------------------------------------------------- /docs/reference/.gitignore: -------------------------------------------------------------------------------- 1 | *.rst 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs >= 20.1.0 2 | furo 3 | importlib_resources 4 | ipykernel 5 | ipython 6 | matplotlib >= 2.2.0 7 | nbsphinx 8 | nibabel 9 | nipype >= 1.5.1 10 | niworkflows >= 1.7.0 11 | numpy 12 | packaging 13 | pandoc 14 | pydot >= 1.2.3 15 | pydotplus 16 | scipy 17 | sphinx >= 7.2.2 18 | sphinx-argparse 19 | sphinxcontrib-apidoc 20 | templateflow 21 | -------------------------------------------------------------------------------- /docs/tools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | These files were obtained from 2 | 3 | https://www.mail-archive.com/sphinx-dev@googlegroups.com/msg02472.html 4 | 5 | and were released under a BSD/MIT license by Fernando Perez, Matthew Brett and 6 | the PyMVPA folks. Further cleanups by the scikit-image crew. 7 | -------------------------------------------------------------------------------- /docs/tools/buildmodref.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Script to auto-generate API docs.""" 3 | 4 | from __future__ import division, print_function 5 | 6 | # stdlib imports 7 | import sys 8 | 9 | # version comparison 10 | from distutils.version import LooseVersion as V 11 | 12 | # local imports 13 | from apigen import ApiDocWriter 14 | 15 | # ***************************************************************************** 16 | 17 | 18 | def abort(error): 19 | print('*WARNING* API documentation not generated: %s' % error) 20 | exit() 21 | 22 | 23 | def writeapi(package, outdir, source_version, other_defines=True): 24 | # Check that the package is available. If not, the API documentation is not 25 | # (re)generated and existing API documentation sources will be used. 26 | 27 | try: 28 | __import__(package) 29 | except ImportError: 30 | abort('Can not import ' + package) 31 | 32 | module = sys.modules[package] 33 | 34 | # Check that the source version is equal to the installed 35 | # version. If the versions mismatch the API documentation sources 36 | # are not (re)generated. This avoids automatic generation of documentation 37 | # for older or newer versions if such versions are installed on the system. 38 | 39 | installed_version = V(module.__version__) 40 | if source_version != installed_version: 41 | abort('Installed version does not match source version') 42 | 43 | docwriter = ApiDocWriter(package, rst_extension='.rst', other_defines=other_defines) 44 | 45 | docwriter.package_skip_patterns += [ 46 | r'\.%s$' % package, 47 | r'.*test.*$', 48 | r'\.version.*$', 49 | ] 50 | docwriter.write_api_docs(outdir) 51 | docwriter.write_index(outdir, 'index', relative_to=outdir) 52 | print('%d files written' % len(docwriter.written_modules)) 53 | 54 | 55 | if __name__ == '__main__': 56 | package = sys.argv[1] 57 | outdir = sys.argv[2] 58 | try: 59 | other_defines = sys.argv[3] 60 | except IndexError: 61 | other_defines = True 62 | else: 63 | other_defines = other_defines in ('True', 'true', '1') 64 | 65 | writeapi(package, outdir, other_defines=other_defines) 66 | -------------------------------------------------------------------------------- /env.yml: -------------------------------------------------------------------------------- 1 | name: sdcflows 2 | channels: 3 | - https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/ 4 | - conda-forge 5 | # Update this ~yearly; last updated Mar 2025 6 | dependencies: 7 | - python=3.12 8 | # Intel Math Kernel Library for numpy 9 | - mkl=2024.2.2 10 | - mkl-service=2.4.2 11 | # Base scientific python stack; required by FSL, so pinned here 12 | - numpy=1.26 13 | - scipy=1.15 14 | - matplotlib=3.9 15 | - pandas=2.2 16 | - h5py=3.13 17 | # Dependencies compiled against numpy, best to stick with conda 18 | - scikit-image=0.25 19 | # Utilities 20 | - graphviz=11.0 21 | # Workflow dependencies: ANTs 22 | - ants=2.5 23 | # 5.4.1 and 5.4.2 cause segfaults with ants 24 | # Try to remove this ASAP 25 | # https://github.com/conda-forge/ants-feedstock/issues/19 26 | - libitk=5.4.0 27 | # Workflow dependencies: FSL (versions pinned in 6.0.7.13) 28 | - fsl-fugue=2201.5 29 | - fsl-topup=2203.5 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "hatch-vcs", "nipreps-versions"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "sdcflows" 7 | description = "Susceptibility Distortion Correction (SDC) workflows for EPI MR schemes." 8 | readme = "README.rst" 9 | authors = [{name = "The NiPreps Developers", email = "nipreps@gmail.com"}] 10 | classifiers = [ 11 | "Development Status :: 4 - Beta", 12 | "Intended Audience :: Science/Research", 13 | "Topic :: Scientific/Engineering :: Image Recognition", 14 | "Topic :: Scientific/Engineering :: Bio-Informatics", 15 | "License :: OSI Approved :: Apache Software License", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | ] 22 | license = "Apache-2.0" 23 | requires-python = ">=3.9" 24 | dependencies = [ 25 | "acres >= 0.2.0", 26 | "attrs >= 20.1.0", 27 | "nibabel >= 3.0", 28 | "nipype >= 1.8.5", 29 | "migas >= 0.4.0", 30 | "nireports >= 25.0.1", 31 | "niworkflows >= 1.11.0", 32 | "nitransforms >= 24.1.0", 33 | "numpy >= 1.23", 34 | "pybids >= 0.16.4", 35 | "scikit-image >= 0.18", 36 | "scipy >= 1.8.1", 37 | "templateflow >= 23.1", 38 | "toml >= 0.10", 39 | ] 40 | dynamic = ["version"] 41 | 42 | [project.urls] 43 | Documentation = "https://www.nipreps.org/sdcflows" 44 | Home = "https://github.com/nipreps/sdcflows" 45 | NiPreps = "https://www.nipreps.org/" 46 | 47 | [project.optional-dependencies] 48 | doc = [ 49 | "attrs >= 20.1.0", 50 | "furo", 51 | "importlib_resources", 52 | "ipykernel", 53 | "ipython", 54 | "nbsphinx", 55 | "pandoc", 56 | "pydot >= 1.2.3", 57 | "pydotplus", 58 | "sphinx >= 7.2.2", 59 | "sphinx-argparse", 60 | "sphinxcontrib-apidoc", 61 | ] 62 | 63 | mem = [ 64 | "psutil" 65 | ] 66 | 67 | dev = [ 68 | "pre-commit", 69 | "ruff", 70 | ] 71 | 72 | test = [ 73 | "coverage[toml] >=5.2.1", 74 | "pytest >= 6", 75 | "pytest-cov >= 2.11", 76 | "pytest-env", 77 | "pytest-xdist >= 2.5", 78 | ] 79 | 80 | # Aliases 81 | docs = ["sdcflows[doc]"] 82 | tests = ["sdcflows[test]"] 83 | all = ["sdcflows[doc,test,mem,dev,test]"] 84 | 85 | [project.scripts] 86 | sdcflows = "sdcflows.cli.main:main" 87 | 88 | # 89 | # Hatch configurations 90 | # 91 | 92 | [tool.hatch.metadata] 93 | allow-direct-references = true 94 | 95 | [tool.hatch.build.targets.sdist] 96 | exclude = [".git_archival.txt"] # No longer needed in sdist 97 | 98 | [tool.hatch.build.targets.wheel] 99 | packages = ["sdcflows"] 100 | exclude = [ 101 | "sdcflows/tests/data", # Large test data directory 102 | ] 103 | 104 | ## The following two sections configure setuptools_scm in the hatch way 105 | 106 | [tool.hatch.version] 107 | validate-bump = true 108 | source = "vcs" 109 | raw-options = { version_scheme = "nipreps-calver" } 110 | 111 | [tool.hatch.build.hooks.vcs] 112 | version-file = "sdcflows/_version.py" 113 | 114 | # 115 | # Developer tool configurations 116 | # 117 | 118 | # Disable black, use ruff below 119 | [tool.black] 120 | exclude = "*" 121 | 122 | [tool.pytest.ini_options] 123 | minversion = "6" 124 | testpaths = ["sdcflows"] 125 | log_cli_level = "INFO" 126 | xfail_strict = true 127 | norecursedirs = [".git"] 128 | addopts = [ 129 | "-svx", 130 | "-ra", 131 | "--strict-config", 132 | "--strict-markers", 133 | "--doctest-modules", 134 | # Config pytest-cov 135 | "--cov=sdcflows", 136 | "--cov-report=xml", 137 | "--cov-config=pyproject.toml", 138 | ] 139 | doctest_optionflags = "ALLOW_UNICODE NORMALIZE_WHITESPACE ELLIPSIS" 140 | env = "PYTHONHASHSEED=0" 141 | filterwarnings = ["ignore::DeprecationWarning"] 142 | junit_family = "xunit2" 143 | markers = [ 144 | "slow: marks tests as slow (deselect with '-m \"not slow\"')", 145 | "veryslow: marks tests as very slow (>5min)", 146 | ] 147 | 148 | 149 | [tool.coverage.run] 150 | branch = true 151 | omit = [ 152 | '*/tests/*', 153 | '*/__init__.py', 154 | '*/conftest.py', 155 | 'sdcflows/_version.py' 156 | ] 157 | 158 | [tool.coverage.report] 159 | # Regexes for lines to exclude from consideration 160 | exclude_lines = [ 161 | 'raise NotImplementedError', 162 | 'warnings\.warn', 163 | ] 164 | 165 | [tool.codespell] 166 | # nd - import scipy.ndimage as nd 167 | # mapp, reson -- Mapp. and Reson. abbreviations in citation 168 | ignore-words-list = 'nd,mapp,reson' 169 | skip = """ 170 | ./.git,*.pdf,*.svg,*.min.js,*.ipynb,ORIGINAL_LICENSE,\ 171 | ./docs/source/_static/example_anatreport.html""" 172 | 173 | [tool.check-wheel-contents] 174 | ignore = [ 175 | "W002", # Test data contains duplicates 176 | ] 177 | 178 | [tool.ruff] 179 | line-length = 99 180 | 181 | [tool.ruff.lint] 182 | select = [ 183 | "F", 184 | "E", 185 | "W", 186 | "I", 187 | ] 188 | ignore = [ 189 | "E203", 190 | "B019", 191 | "SIM108", 192 | "C901", 193 | ] 194 | 195 | [tool.ruff.lint.flake8-quotes] 196 | inline-quotes = "single" 197 | 198 | [tool.ruff.lint.extend-per-file-ignores] 199 | "*/__init__.py" = ["F401"] 200 | "docs/conf.py" = ["E265"] 201 | 202 | [tool.ruff.format] 203 | quote-style = "single" 204 | -------------------------------------------------------------------------------- /sdcflows/__init__.py: -------------------------------------------------------------------------------- 1 | """SDCflows - :abbr:`SDC (susceptibility distortion correction)` by DUMMIES, for dummies.""" 2 | 3 | __packagename__ = 'sdcflows' 4 | __copyright__ = '2022, The NiPreps developers' 5 | try: 6 | from ._version import __version__ 7 | except ModuleNotFoundError: 8 | from importlib.metadata import PackageNotFoundError, version 9 | 10 | try: 11 | __version__ = version(__packagename__) 12 | except PackageNotFoundError: 13 | __version__ = '0+unknown' 14 | del version 15 | del PackageNotFoundError 16 | -------------------------------------------------------------------------------- /sdcflows/_warnings.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2023 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | # STATEMENT OF CHANGES: This file is derived from sources licensed under the Apache-2.0 terms, 24 | # and this file has been changed. 25 | # The original file this work derives from is found at: 26 | # https://github.com/nipreps/mriqc/blob/8ceadba8669cc2a86119a97b9311ab968f11c6eb/mriqc/_warnings.py 27 | """Manipulate Python warnings.""" 28 | 29 | import logging 30 | import warnings 31 | 32 | _wlog = logging.getLogger('py.warnings') 33 | _wlog.addHandler(logging.NullHandler()) 34 | 35 | 36 | def _warn(message, category=None, stacklevel=1, source=None): 37 | """Redefine the warning function.""" 38 | if category is not None: 39 | category = type(category).__name__ 40 | category = category.replace('type', 'WARNING') 41 | 42 | logging.getLogger('py.warnings').warning(f'{category or "WARNING"}: {message}') 43 | 44 | 45 | def _showwarning(message, category, filename, lineno, file=None, line=None): 46 | _warn(message, category=category) 47 | 48 | 49 | warnings.warn = _warn 50 | warnings.showwarning = _showwarning 51 | -------------------------------------------------------------------------------- /sdcflows/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/cli/__init__.py -------------------------------------------------------------------------------- /sdcflows/cli/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/cli/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/cli/tests/test_find_estimators.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2023 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Check the CLI.""" 24 | 25 | from importlib import reload 26 | 27 | import pytest 28 | from niworkflows.utils.testing import generate_bids_skeleton 29 | 30 | from sdcflows.cli.main import main as cli_finder_wrapper 31 | from sdcflows.fieldmaps import clear_registry 32 | 33 | OUTPUT = """\ 34 | Estimation for <{path}> complete. Found: 35 | \tsub-01 36 | \t\tFieldmapEstimation(sources=<2 files>, method=, \ 37 | bids_id='{estimator_id}') 38 | \t\t\tj-\tfmap/sub-01_dir-AP_epi.nii.gz 39 | \t\t\tj\tfmap/sub-01_dir-PA_epi.nii.gz 40 | \tsub-02 41 | \t\tNo estimators found 42 | """ 43 | 44 | intendedfor_config = { 45 | '01': [ 46 | {'anat': [{'suffix': 'T1w', 'metadata': {'EchoTime': 1}}]}, 47 | { 48 | 'fmap': [ 49 | { 50 | 'dir': 'AP', 51 | 'suffix': 'epi', 52 | 'metadata': { 53 | 'TotalReadoutTime': 0.1, 54 | 'PhaseEncodingDirection': 'j-', 55 | 'IntendedFor': 'func/sub-01_task-rest_bold.nii.gz', 56 | }, 57 | }, 58 | { 59 | 'dir': 'PA', 60 | 'suffix': 'epi', 61 | 'metadata': { 62 | 'TotalReadoutTime': 0.1, 63 | 'PhaseEncodingDirection': 'j', 64 | 'IntendedFor': 'func/sub-01_task-rest_bold.nii.gz', 65 | }, 66 | }, 67 | ] 68 | }, 69 | { 70 | 'func': [ 71 | { 72 | 'task': 'rest', 73 | 'suffix': 'bold', 74 | 'metadata': { 75 | 'RepetitionTime': 0.8, 76 | 'PhaseEncodingDirection': 'j', 77 | }, 78 | } 79 | ] 80 | }, 81 | ], 82 | '02': [{'anat': [{'suffix': 'T1w', 'metadata': {'EchoTime': 1}}]}], 83 | } 84 | 85 | 86 | b0field_config = { 87 | '01': [ 88 | {'anat': [{'suffix': 'T1w', 'metadata': {'EchoTime': 1}}]}, 89 | { 90 | 'fmap': [ 91 | { 92 | 'dir': 'AP', 93 | 'suffix': 'epi', 94 | 'metadata': { 95 | 'B0FieldIdentifier': 'pepolar', 96 | 'TotalReadoutTime': 0.1, 97 | 'PhaseEncodingDirection': 'j-', 98 | }, 99 | }, 100 | { 101 | 'dir': 'PA', 102 | 'suffix': 'epi', 103 | 'metadata': { 104 | 'B0FieldIdentifier': 'pepolar', 105 | 'TotalReadoutTime': 0.1, 106 | 'PhaseEncodingDirection': 'j', 107 | }, 108 | }, 109 | ] 110 | }, 111 | { 112 | 'func': [ 113 | { 114 | 'task': 'rest', 115 | 'suffix': 'bold', 116 | 'metadata': { 117 | 'B0FieldSource': 'pepolar', 118 | 'RepetitionTime': 0.8, 119 | 'PhaseEncodingDirection': 'j', 120 | }, 121 | } 122 | ] 123 | }, 124 | ], 125 | '02': [{'anat': [{'suffix': 'T1w', 'metadata': {'EchoTime': 1}}]}], 126 | } 127 | 128 | 129 | @pytest.mark.parametrize( 130 | 'test_id,config,estimator_id', 131 | [ 132 | ('intendedfor', intendedfor_config, 'auto_00000'), 133 | ('b0field', b0field_config, 'pepolar'), 134 | ], 135 | ) 136 | def test_cli_finder_wrapper(tmp_path, capsys, test_id, config, estimator_id): 137 | """Test the CLI with --dry-run.""" 138 | import sdcflows.config as sc 139 | 140 | # Reload is necessary to clean-up the layout config between parameterized runs 141 | reload(sc) 142 | 143 | path = (tmp_path / test_id).absolute() 144 | generate_bids_skeleton(path, config) 145 | with pytest.raises(SystemExit) as wrapped_exit: 146 | cli_finder_wrapper([str(path), str(tmp_path / 'out'), 'participant', '--dry-run']) 147 | 148 | assert wrapped_exit.value.code == 0 149 | output = OUTPUT.format(path=path, estimator_id=estimator_id) 150 | out, _ = capsys.readouterr() 151 | assert out == output 152 | clear_registry() 153 | -------------------------------------------------------------------------------- /sdcflows/cli/workflow.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2023 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Build the SDCFlows' standalone workflow.""" 24 | 25 | 26 | def build_workflow(config_file, retval): 27 | """Create the Nipype Workflow that supports the whole execution graph.""" 28 | import os 29 | 30 | # We do not need OMP > 1 for workflow creation 31 | os.environ['OMP_NUM_THREADS'] = '1' 32 | 33 | from sdcflows import config 34 | from sdcflows.workflows.fit.base import init_sdcflows_wf 35 | 36 | config.load(config_file) 37 | # Initialize nipype config 38 | config.nipype.init() 39 | # Make sure loggers are started 40 | config.loggers.init() 41 | 42 | config.loggers.cli.log( 43 | 25, 44 | f"""\ 45 | Running SDCFlows {config.environment.version}: 46 | * BIDS dataset path: {config.execution.bids_dir}. 47 | * Output folder: {config.execution.output_dir}. 48 | * Analysis levels: {config.workflow.analysis_level}. 49 | """, 50 | ) 51 | 52 | retval['return_code'] = 1 53 | retval['workflow'] = None 54 | retval['workflow'] = init_sdcflows_wf() 55 | retval['return_code'] = int(retval['workflow'] is None) 56 | return retval 57 | -------------------------------------------------------------------------------- /sdcflows/conftest.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """py.test configuration.""" 24 | 25 | import logging 26 | import os 27 | from pathlib import Path 28 | 29 | import nibabel 30 | import numpy 31 | import pytest 32 | from bids.layout import BIDSLayout 33 | 34 | from .fieldmaps import clear_registry 35 | 36 | # disable ET 37 | os.environ['NO_ET'] = '1' 38 | 39 | test_data_env = os.getenv('TEST_DATA_HOME', str(Path.home() / 'sdcflows-tests')) 40 | test_output_dir = os.getenv('TEST_OUTPUT_DIR') 41 | test_workdir = os.getenv('TEST_WORK_DIR') 42 | _sloppy_mode = os.getenv('TEST_PRODUCTION', 'off').lower() not in ('on', '1', 'true', 'yes', 'y') 43 | 44 | layouts = { 45 | p.name: BIDSLayout(str(p), validate=False, derivatives=True) 46 | for p in Path(test_data_env).glob('*') 47 | if p.is_dir() 48 | } 49 | 50 | data_dir = Path(__file__).parent / 'tests' / 'data' 51 | layouts.update( 52 | { 53 | folder.name: BIDSLayout(folder, validate=False, derivatives=False) 54 | for folder in data_dir.glob('ds*') 55 | if folder.is_dir() 56 | } 57 | ) 58 | 59 | 60 | def pytest_report_header(config): 61 | return f"""\ 62 | TEST_DATA_HOME={test_data_env} 63 | -> Available datasets: {', '.join(layouts.keys())}. 64 | TEST_OUTPUT_DIR={test_output_dir or ' (output files will be discarded)'}. 65 | TEST_WORK_DIR={test_workdir or ' (intermediate files will be discarded)'}. 66 | """ 67 | 68 | 69 | @pytest.fixture(autouse=True) 70 | def doctest_fixture(doctest_namespace, request, caplog): 71 | doctest_plugin = request.config.pluginmanager.getplugin('doctest') 72 | if isinstance(request.node, doctest_plugin.DoctestItem): 73 | doctest_namespace.update( 74 | np=numpy, 75 | nb=nibabel, 76 | os=os, 77 | Path=Path, 78 | layouts=layouts, 79 | dsA_dir=data_dir / 'dsA', 80 | dsB_dir=data_dir / 'dsB', 81 | dsC_dir=data_dir / 'dsC', 82 | data_dir=data_dir, 83 | caplog=caplog, 84 | logging=logging, 85 | ) 86 | doctest_namespace.update((key, Path(val.root)) for key, val in layouts.items()) 87 | 88 | # Start every doctest clean, and clean up after ourselves 89 | clear_registry() 90 | yield 91 | clear_registry() 92 | else: 93 | yield 94 | 95 | 96 | @pytest.fixture 97 | def workdir(): 98 | return None if test_workdir is None else Path(test_workdir) 99 | 100 | 101 | @pytest.fixture 102 | def outdir(): 103 | return None if test_output_dir is None else Path(test_output_dir) 104 | 105 | 106 | @pytest.fixture 107 | def bids_layouts(): 108 | if layouts: 109 | return layouts 110 | pytest.skip() 111 | 112 | 113 | @pytest.fixture 114 | def datadir(): 115 | return Path(test_data_env) 116 | 117 | 118 | @pytest.fixture 119 | def testdata_dir(): 120 | return data_dir 121 | 122 | 123 | @pytest.fixture 124 | def dsA_dir(): 125 | return data_dir / 'dsA' 126 | 127 | 128 | @pytest.fixture 129 | def sloppy_mode(): 130 | return _sloppy_mode 131 | -------------------------------------------------------------------------------- /sdcflows/data/__init__.py: -------------------------------------------------------------------------------- 1 | """SDCFlows data files 2 | 3 | .. autofunction:: load 4 | 5 | .. automethod:: load.readable 6 | 7 | .. automethod:: load.as_path 8 | 9 | .. automethod:: load.cached 10 | 11 | .. autoclass:: Loader 12 | """ 13 | 14 | from acres import Loader 15 | 16 | load = Loader(__spec__.name) 17 | -------------------------------------------------------------------------------- /sdcflows/data/affine.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse_output_transforms": true, 3 | "convergence_threshold": [ 1e-06, 1e-07 ], 4 | "convergence_window_size": [ 10, 5 ], 5 | "dimension": 3, 6 | "initial_moving_transform_com": 1, 7 | "interpolation": "Linear", 8 | "metric": [ "Mattes", "Mattes" ], 9 | "metric_weight": [ 1.0, 1.0 ], 10 | "number_of_iterations": [ [ 1000, 0 ], [ 250, 100 ] ], 11 | "radius_or_number_of_bins": [ 32, 32 ], 12 | "sampling_percentage": [ 1.0, 0.4 ], 13 | "sampling_strategy": [ "Regular", "Random" ], 14 | "shrink_factors": [[ 4, 1] , [ 2, 1 ]], 15 | "sigma_units": [ "mm", "vox" ], 16 | "smoothing_sigmas": [ [ 6, 0 ], [ 2, 1 ] ], 17 | "transform_parameters": [ [ 2.0 ], [ 1.0 ] ], 18 | "transforms": [ "Rigid", "Affine" ], 19 | "use_histogram_matching": [ true, true ], 20 | "winsorize_lower_quantile": 0.001, 21 | "winsorize_upper_quantile": 0.998, 22 | "write_composite_transform": false 23 | } 24 | -------------------------------------------------------------------------------- /sdcflows/data/flirtsch/b02b0.cnf: -------------------------------------------------------------------------------- 1 | # Resolution (knot-spacing) of warps in mm 2 | --warpres=14,12,10,6,4,4,4 3 | # Subsampling level (a value of 2 indicates that a 2x2x2 neighbourhood is collapsed to 1 voxel) 4 | --subsamp=2,2,2,1,1,1,1 5 | # FWHM of gaussian smoothing 6 | --fwhm=6,4,3,2,1,0,0 7 | # Maximum number of iterations 8 | --miter=5,5,5,10,10,20,20 9 | # Relative weight of regularisation 10 | --lambda=0.001,0.0001,0.000005,0.0000005,0.00000005,0.0000000005,0.00000000001 11 | # If set to 1 lambda is multiplied by the current average squared difference 12 | --ssqlambda=1 13 | # Regularisation model 14 | --regmod=bending_energy 15 | # If set to 1 movements are estimated along with the field 16 | --estmov=0 17 | # 0=Levenberg-Marquardt, 1=Scaled Conjugate Gradient 18 | --minmet=0,0,0,1,1,1,1 19 | # Quadratic or cubic splines 20 | --splineorder=3 21 | # Precision for calculation and storage of Hessian 22 | --numprec=double 23 | # Linear or spline interpolation 24 | --interp=spline 25 | # If set to 1 the images are individually scaled to a common mean intensity 26 | --scale=1 -------------------------------------------------------------------------------- /sdcflows/data/flirtsch/b02b0_1.cnf: -------------------------------------------------------------------------------- 1 | # Resolution (knot-spacing) of warps in mm 2 | --warpres=20,16,14,12,10,6,4,4,4 3 | # Subsampling level (a value of 2 indicates that a 2x2x2 neighbourhood is collapsed to 1 voxel) 4 | --subsamp=1,1,1,1,1,1,1,1,1 5 | # FWHM of gaussian smoothing 6 | --fwhm=8,6,4,3,3,2,1,0,0 7 | # Maximum number of iterations 8 | --miter=5,5,5,5,5,10,10,20,20 9 | # Relative weight of regularisation 10 | --lambda=0.0005,0.0001,0.00001,0.0000015,0.0000005,0.0000005,0.00000005,0.0000000005,0.00000000001 11 | # If set to 1 lambda is multiplied by the current average squared difference 12 | --ssqlambda=1 13 | # Regularisation model 14 | --regmod=bending_energy 15 | # If set to 1 movements are estimated along with the field 16 | --estmov=1,1,1,1,1,0,0,0,0 17 | # 0=Levenberg-Marquardt, 1=Scaled Conjugate Gradient 18 | --minmet=0,0,0,0,0,1,1,1,1 19 | # Quadratic or cubic splines 20 | --splineorder=3 21 | # Precision for calculation and storage of Hessian 22 | --numprec=double 23 | # Linear or spline interpolation 24 | --interp=spline 25 | # If set to 1 the images are individually scaled to a common mean intensity 26 | --scale=1 -------------------------------------------------------------------------------- /sdcflows/data/flirtsch/b02b0_2.cnf: -------------------------------------------------------------------------------- 1 | # Resolution (knot-spacing) of warps in mm 2 | --warpres=20,16,14,12,10,6,4,4,4 3 | # Subsampling level (a value of 2 indicates that a 2x2x2 neighbourhood is collapsed to 1 voxel) 4 | --subsamp=2,2,2,2,2,1,1,1,1 5 | # FWHM of gaussian smoothing 6 | --fwhm=8,6,4,3,3,2,1,0,0 7 | # Maximum number of iterations 8 | --miter=5,5,5,5,5,10,10,20,20 9 | # Relative weight of regularisation 10 | --lambda=0.005,0.001,0.0001,0.000015,0.000005,0.0000005,0.00000005,0.0000000005,0.00000000001 11 | # If set to 1 lambda is multiplied by the current average squared difference 12 | --ssqlambda=1 13 | # Regularisation model 14 | --regmod=bending_energy 15 | # If set to 1 movements are estimated along with the field 16 | --estmov=1,1,1,1,1,0,0,0,0 17 | # 0=Levenberg-Marquardt, 1=Scaled Conjugate Gradient 18 | --minmet=0,0,0,0,0,1,1,1,1 19 | # Quadratic or cubic splines 20 | --splineorder=3 21 | # Precision for calculation and storage of Hessian 22 | --numprec=double 23 | # Linear or spline interpolation 24 | --interp=spline 25 | # If set to 1 the images are individually scaled to a common mean intensity 26 | --scale=1 -------------------------------------------------------------------------------- /sdcflows/data/flirtsch/b02b0_4.cnf: -------------------------------------------------------------------------------- 1 | # Resolution (knot-spacing) of warps in mm 2 | --warpres=20,16,14,12,10,6,4,4,4 3 | # Subsampling level (a value of 2 indicates that a 2x2x2 neighbourhood is collapsed to 1 voxel) 4 | --subsamp=4,4,2,2,2,1,1,1,1 5 | # FWHM of gaussian smoothing 6 | --fwhm=8,6,4,3,3,2,1,0,0 7 | # Maximum number of iterations 8 | --miter=5,5,5,5,5,10,10,20,20 9 | # Relative weight of regularisation 10 | --lambda=0.035,0.006,0.0001,0.000015,0.000005,0.0000005,0.00000005,0.0000000005,0.00000000001 11 | # If set to 1 lambda is multiplied by the current average squared difference 12 | --ssqlambda=1 13 | # Regularisation model 14 | --regmod=bending_energy 15 | # If set to 1 movements are estimated along with the field 16 | --estmov=1,1,1,1,1,0,0,0,0 17 | # 0=Levenberg-Marquardt, 1=Scaled Conjugate Gradient 18 | --minmet=0,0,0,0,0,1,1,1,1 19 | # Quadratic or cubic splines 20 | --splineorder=3 21 | # Precision for calculation and storage of Hessian 22 | --numprec=double 23 | # Linear or spline interpolation 24 | --interp=spline 25 | # If set to 1 the images are individually scaled to a common mean intensity 26 | --scale=1 -------------------------------------------------------------------------------- /sdcflows/data/flirtsch/b02b0_quick.cnf: -------------------------------------------------------------------------------- 1 | # Resolution (knot-spacing) of warps in mm 2 | --warpres=20,14,10 3 | # Subsampling level (a value of 2 indicates that a 2x2x2 neighbourhood is collapsed to 1 voxel) 4 | --subsamp=2,2,2 5 | # FWHM of gaussian smoothing 6 | --fwhm=8,2,1 7 | # Maximum number of iterations 8 | --miter=5,5,5 9 | # Relative weight of regularisation 10 | --lambda=0.005,0.000005,0.0000005 11 | # If set to 1 lambda is multiplied by the current average squared difference 12 | --ssqlambda=1 13 | # Regularisation model 14 | --regmod=bending_energy 15 | # If set to 1 movements are estimated along with the field 16 | --estmov=0,0,0 17 | # 0=Levenberg-Marquardt, 1=Scaled Conjugate Gradient 18 | --minmet=0,0,1 19 | # Quadratic or cubic splines 20 | --splineorder=3 21 | # Precision for calculation and storage of Hessian 22 | --numprec=double 23 | # Linear or spline interpolation 24 | --interp=spline 25 | # If set to 1 the images are individually scaled to a common mean intensity 26 | --scale=1 -------------------------------------------------------------------------------- /sdcflows/data/fmap-any_registration.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse_output_transforms": true, 3 | "convergence_threshold": [ 1e-07, 1e-08 ], 4 | "convergence_window_size": [ 5, 3 ], 5 | "dimension": 3, 6 | "initial_moving_transform_com": 1, 7 | "interpolation": "LanczosWindowedSinc", 8 | "metric": [ "Mattes", "Mattes" ], 9 | "metric_weight": [ 1.0, 1.0 ], 10 | "number_of_iterations": [ [ 50 ], [ 20 ] ], 11 | "radius_or_number_of_bins": [ 32, 32 ], 12 | "sampling_percentage": [ 0.25, 0.5 ], 13 | "sampling_strategy": [ "Random", "Random" ], 14 | "shrink_factors": [ [ 2 ], [ 1 ] ], 15 | "sigma_units": [ "mm", "mm" ], 16 | "smoothing_sigmas": [ [ 8.0 ], [ 2.0 ] ], 17 | "transform_parameters": [ [ 0.1 ], [ 0.1 ] ], 18 | "transforms": [ "Rigid", "Rigid" ], 19 | "use_histogram_matching": [ true, true ], 20 | "winsorize_lower_quantile": 0.001, 21 | "winsorize_upper_quantile": 0.999, 22 | "write_composite_transform": false 23 | } -------------------------------------------------------------------------------- /sdcflows/data/fmap-any_registration_testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse_output_transforms": true, 3 | "convergence_threshold": [ 1e-07, 1e-08 ], 4 | "convergence_window_size": [ 5, 3 ], 5 | "dimension": 3, 6 | "initial_moving_transform_com": 1, 7 | "interpolation": "LanczosWindowedSinc", 8 | "metric": [ "Mattes", "Mattes" ], 9 | "metric_weight": [ 1.0, 1.0 ], 10 | "number_of_iterations": [ [ 50 ], [ 10 ] ], 11 | "radius_or_number_of_bins": [ 32, 32 ], 12 | "sampling_percentage": [ 0.25, 0.5 ], 13 | "sampling_strategy": [ "Regular", "Regular" ], 14 | "shrink_factors": [ [ 2 ], [ 1 ] ], 15 | "sigma_units": [ "mm", "mm" ], 16 | "smoothing_sigmas": [ [ 8.0 ], [ 2.0 ] ], 17 | "transform_parameters": [ [ 1.0 ], [ 0.5 ] ], 18 | "transforms": [ "Rigid", "Rigid" ], 19 | "use_histogram_matching": [ true, true ], 20 | "winsorize_lower_quantile": 0.005, 21 | "winsorize_upper_quantile": 0.998, 22 | "write_composite_transform": false 23 | } -------------------------------------------------------------------------------- /sdcflows/data/fmap_atlas.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/data/fmap_atlas.nii.gz -------------------------------------------------------------------------------- /sdcflows/data/fmap_atlas_2_MNI152NLin2009cAsym_affine.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/data/fmap_atlas_2_MNI152NLin2009cAsym_affine.mat -------------------------------------------------------------------------------- /sdcflows/data/sd_syn.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse_output_transforms": true, 3 | "convergence_threshold": [ 1e-06, 1e-08 ], 4 | "convergence_window_size": [ 5, 2 ], 5 | "dimension": 3, 6 | "interpolation": "Linear", 7 | "metric": [ ["Mattes", "Mattes"], ["Mattes", "Mattes"] ], 8 | "metric_weight": [ [0.9, 0.1], [0.8, 0.2] ], 9 | "number_of_iterations": [ [ 200, 100 ], [ 10 ] ], 10 | "output_transform_prefix": "fmap_syn", 11 | "radius_or_number_of_bins": [ [48, 48], [48, 48] ], 12 | "sampling_percentage": [ [0.8, 0.8], [1.0, 1.0] ], 13 | "sampling_strategy": [["Random", "Random"], [null, null] ], 14 | "shrink_factors": [ [ 1, 1 ], [ 1 ] ], 15 | "sigma_units": [ "vox", "vox" ], 16 | "smoothing_sigmas": [ [ 2, 0 ], [ 0 ] ], 17 | "transform_parameters": [ [ 0.8, 6.0, 10.0 ], [ 0.8, 2.0, 0.5 ] ], 18 | "transforms": [ "SyN", "SyN" ], 19 | "use_histogram_matching": [ true, true ], 20 | "winsorize_lower_quantile": 0.001, 21 | "winsorize_upper_quantile": 0.998, 22 | "write_composite_transform": false 23 | } 24 | -------------------------------------------------------------------------------- /sdcflows/data/sd_syn_sloppy.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse_output_transforms": true, 3 | "convergence_threshold": [ 1e-06, 1e-08 ], 4 | "convergence_window_size": [ 5, 2 ], 5 | "dimension": 3, 6 | "interpolation": "Linear", 7 | "metric": [ ["Mattes", "Mattes"], ["Mattes", "Mattes"] ], 8 | "metric_weight": [ [0.9, 0.1], [0.8, 0.2] ], 9 | "number_of_iterations": [ [ 20, 10 ], [ 2 ] ], 10 | "output_transform_prefix": "fmap_syn", 11 | "radius_or_number_of_bins": [ [48, 48], [48, 48] ], 12 | "sampling_percentage": [ [0.8, 0.8], [1.0, 1.0] ], 13 | "sampling_strategy": [["Random", "Random"], [null, null] ], 14 | "shrink_factors": [ [ 1, 1 ], [ 1 ] ], 15 | "sigma_units": [ "vox", "vox" ], 16 | "smoothing_sigmas": [ [ 2, 0 ], [ 0 ] ], 17 | "transform_parameters": [ [ 0.8, 6.0, 10.0 ], [ 0.8, 2.0, 0.5 ] ], 18 | "transforms": [ "SyN", "SyN" ], 19 | "use_histogram_matching": [ true, true ], 20 | "verbose": true, 21 | "winsorize_lower_quantile": 0.001, 22 | "winsorize_upper_quantile": 0.998, 23 | "write_composite_transform": false 24 | } 25 | -------------------------------------------------------------------------------- /sdcflows/data/translation_rigid.json: -------------------------------------------------------------------------------- 1 | { 2 | "dimension": 3, 3 | "float": true, 4 | "winsorize_lower_quantile": 0.005, 5 | "winsorize_upper_quantile": 0.998, 6 | "collapse_output_transforms": true, 7 | "write_composite_transform": true, 8 | "use_histogram_matching": [ true, true ], 9 | "transforms": [ "Translation", "Rigid" ], 10 | "number_of_iterations": [ [ 500 ], [ 200 ] ], 11 | "transform_parameters": [ [ 0.05 ], [ 0.01 ] ], 12 | "convergence_threshold": [ 1e-07, 1e-08 ], 13 | "convergence_window_size": [ 200, 100 ], 14 | "metric": [ "Mattes", "Mattes" ], 15 | "sampling_percentage": [ 0.5, 0.5 ], 16 | "sampling_strategy": [ "Random", "Random" ], 17 | "smoothing_sigmas": [ [ 8.0 ], [ 2.0 ] ], 18 | "sigma_units": [ "mm", "mm" ], 19 | "metric_weight": [ 1.0, 1.0 ], 20 | "shrink_factors": [ [ 2 ], [ 1 ] ], 21 | "radius_or_number_of_bins": [ 64, 64 ], 22 | "interpolation": "LanczosWindowedSinc" 23 | } -------------------------------------------------------------------------------- /sdcflows/interfaces/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/interfaces/__init__.py -------------------------------------------------------------------------------- /sdcflows/interfaces/brainmask.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Brain extraction interfaces.""" 24 | 25 | from nipype.interfaces.base import ( 26 | BaseInterfaceInputSpec, 27 | File, 28 | SimpleInterface, 29 | TraitedSpec, 30 | traits, 31 | ) 32 | 33 | from ..utils.tools import brain_masker 34 | 35 | 36 | class _BrainExtractionInputSpec(BaseInterfaceInputSpec): 37 | in_file = File(exists=True, mandatory=True, desc='file to mask') 38 | 39 | 40 | class _BrainExtractionOutputSpec(TraitedSpec): 41 | out_file = File(exists=True, desc='the input file, after masking') 42 | out_mask = File(exists=True, desc='the binary brain mask') 43 | out_probseg = File(exists=True, desc='the probabilistic brain mask') 44 | 45 | 46 | class BrainExtraction(SimpleInterface): 47 | """Brain extraction for EPI and GRE data.""" 48 | 49 | input_spec = _BrainExtractionInputSpec 50 | output_spec = _BrainExtractionOutputSpec 51 | 52 | def _run_interface(self, runtime): 53 | from nipype.utils.filemanip import fname_presuffix 54 | 55 | ( 56 | self._results['out_file'], 57 | self._results['out_probseg'], 58 | self._results['out_mask'], 59 | ) = brain_masker( 60 | self.inputs.in_file, 61 | fname_presuffix(self.inputs.in_file, suffix='_mask', newpath=runtime.cwd), 62 | ) 63 | return runtime 64 | 65 | 66 | class _BinaryDilationInputSpec(BaseInterfaceInputSpec): 67 | in_file = File(exists=True, mandatory=True, desc='binary file to dilate') 68 | radius = traits.Float(3, usedefault=True, desc='structure element (ball) radius') 69 | 70 | 71 | class _BinaryDilationOutputSpec(TraitedSpec): 72 | out_file = File(exists=True, desc='the input file, after binary dilation') 73 | 74 | 75 | class BinaryDilation(SimpleInterface): 76 | """Brain extraction for EPI and GRE data.""" 77 | 78 | input_spec = _BinaryDilationInputSpec 79 | output_spec = _BinaryDilationOutputSpec 80 | 81 | def _run_interface(self, runtime): 82 | self._results['out_file'] = _dilate( 83 | self.inputs.in_file, 84 | self.inputs.radius, 85 | newpath=runtime.cwd, 86 | ) 87 | return runtime 88 | 89 | 90 | class _UnionInputSpec(BaseInterfaceInputSpec): 91 | in1 = File(exists=True, mandatory=True, desc='binary file') 92 | in2 = File(exists=True, mandatory=True, desc='binary file') 93 | 94 | 95 | class _UnionOutputSpec(TraitedSpec): 96 | out_file = File(exists=True, desc='the input file, after binary dilation') 97 | 98 | 99 | class Union(SimpleInterface): 100 | """Brain extraction for EPI and GRE data.""" 101 | 102 | input_spec = _UnionInputSpec 103 | output_spec = _UnionOutputSpec 104 | 105 | def _run_interface(self, runtime): 106 | self._results['out_file'] = _union( 107 | self.inputs.in1, 108 | self.inputs.in2, 109 | newpath=runtime.cwd, 110 | ) 111 | return runtime 112 | 113 | 114 | def _dilate(in_file, radius=3, newpath=None): 115 | """Dilate (binary) input mask.""" 116 | from pathlib import Path 117 | 118 | import nibabel as nb 119 | import numpy as np 120 | from nipype.utils.filemanip import fname_presuffix 121 | from scipy import ndimage 122 | from skimage.morphology import ball 123 | 124 | mask = nb.load(in_file) 125 | newdata = ndimage.binary_dilation(np.asanyarray(mask.dataobj) > 0, ball(radius)) 126 | 127 | hdr = mask.header.copy() 128 | hdr.set_data_dtype('uint8') 129 | out_file = fname_presuffix(in_file, suffix='_dil', newpath=newpath or Path.cwd()) 130 | mask.__class__(newdata.astype('uint8'), mask.affine, hdr).to_filename(out_file) 131 | return out_file 132 | 133 | 134 | def _union(in1, in2, newpath=None): 135 | """Dilate (binary) input mask.""" 136 | from pathlib import Path 137 | 138 | import nibabel as nb 139 | import numpy as np 140 | from nipype.utils.filemanip import fname_presuffix 141 | 142 | mask = nb.load(in1) 143 | data = (np.asanyarray(mask.dataobj) + np.asanyarray(nb.load(in2).dataobj)) > 0 144 | 145 | hdr = mask.header.copy() 146 | hdr.set_data_dtype('uint8') 147 | out_file = fname_presuffix(in1, suffix='_union', newpath=newpath or Path.cwd()) 148 | mask.__class__(data.astype('uint8'), mask.affine, hdr).to_filename(out_file) 149 | return out_file 150 | -------------------------------------------------------------------------------- /sdcflows/interfaces/epi.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Interfaces to deal with the various types of fieldmap sources.""" 24 | 25 | from nipype.interfaces.base import ( 26 | BaseInterfaceInputSpec, 27 | File, 28 | InputMultiObject, 29 | OutputMultiObject, 30 | SimpleInterface, 31 | TraitedSpec, 32 | isdefined, 33 | traits, 34 | ) 35 | 36 | 37 | class _GetReadoutTimeInputSpec(BaseInterfaceInputSpec): 38 | in_file = File(exists=True, desc='EPI image corresponding to the metadata') 39 | metadata = traits.Dict(mandatory=True, desc='metadata corresponding to the inputs') 40 | use_estimate = traits.Bool( 41 | False, usedefault=True, desc='Use "Estimated*" fields to calculate TotalReadoutTime' 42 | ) 43 | fallback = traits.Float(desc='A fallback value, in seconds.') 44 | 45 | 46 | class _GetReadoutTimeOutputSpec(TraitedSpec): 47 | readout_time = traits.Float 48 | pe_direction = traits.Enum('i', 'i-', 'j', 'j-', 'k', 'k-') 49 | pe_dir_fsl = traits.Enum('x', 'x-', 'y', 'y-', 'z', 'z-') 50 | 51 | 52 | class GetReadoutTime(SimpleInterface): 53 | """Calculate the readout time from available metadata.""" 54 | 55 | input_spec = _GetReadoutTimeInputSpec 56 | output_spec = _GetReadoutTimeOutputSpec 57 | 58 | def _run_interface(self, runtime): 59 | from ..utils.epimanip import get_trt 60 | 61 | self._results['readout_time'] = get_trt( 62 | self.inputs.metadata, 63 | self.inputs.in_file if isdefined(self.inputs.in_file) else None, 64 | use_estimate=self.inputs.use_estimate, 65 | fallback=self.inputs.fallback or None, 66 | ) 67 | self._results['pe_direction'] = self.inputs.metadata['PhaseEncodingDirection'] 68 | self._results['pe_dir_fsl'] = ( 69 | self.inputs.metadata['PhaseEncodingDirection'] 70 | .replace('i', 'x') 71 | .replace('j', 'y') 72 | .replace('k', 'z') 73 | ) 74 | return runtime 75 | 76 | 77 | class _SortPEBlipsInputSpec(BaseInterfaceInputSpec): 78 | in_data = InputMultiObject( 79 | File(exists=True), 80 | mandatory=True, 81 | desc='list of input data', 82 | ) 83 | pe_dirs_fsl = InputMultiObject( 84 | traits.Enum('x', 'x-', 'y', 'y-', 'z', 'z-'), 85 | mandatory=True, 86 | desc="list of PE directions, in FSL's conventions", 87 | ) 88 | readout_times = InputMultiObject( 89 | traits.Float, mandatory=True, desc='list of total readout times' 90 | ) 91 | 92 | 93 | class _SortPEBlipsOutputSpec(TraitedSpec): 94 | out_data = OutputMultiObject( 95 | File(), 96 | desc='list of input data', 97 | ) 98 | pe_dirs = OutputMultiObject( 99 | traits.Enum('i', 'i-', 'j', 'j-', 'k', 'k-'), 100 | desc="list of PE directions, in BIDS's conventions", 101 | ) 102 | pe_dirs_fsl = OutputMultiObject( 103 | traits.Enum('x', 'x-', 'y', 'y-', 'z', 'z-'), 104 | desc="list of PE directions, in FSL's conventions", 105 | ) 106 | readout_times = OutputMultiObject(traits.Float, desc='list of total readout times') 107 | 108 | 109 | class SortPEBlips(SimpleInterface): 110 | """Sort PE blips so they are consistently fed into TOPUP.""" 111 | 112 | input_spec = _SortPEBlipsInputSpec 113 | output_spec = _SortPEBlipsOutputSpec 114 | 115 | def _run_interface(self, runtime): 116 | # Put sign first 117 | blips = [f'+{pe[0]}' if len(pe) == 1 else f'-{pe[0]}' for pe in self.inputs.pe_dirs_fsl] 118 | sorted_inputs = sorted( 119 | zip( 120 | blips, 121 | self.inputs.readout_times, 122 | self.inputs.in_data, 123 | ) 124 | ) 125 | 126 | ( 127 | self._results['pe_dirs_fsl'], 128 | self._results['readout_times'], 129 | self._results['out_data'], 130 | ) = zip(*sorted_inputs) 131 | 132 | # Put sign back last 133 | self._results['pe_dirs_fsl'] = [ 134 | pe[1] if pe.startswith('+') else f'{pe[1]}-' for pe in self._results['pe_dirs_fsl'] 135 | ] 136 | self._results['pe_dirs'] = [ 137 | pe.replace('x', 'i').replace('y', 'j').replace('z', 'k') 138 | for pe in self._results['pe_dirs_fsl'] 139 | ] 140 | return runtime 141 | -------------------------------------------------------------------------------- /sdcflows/interfaces/reportlets.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Interfaces to generate speciality reportlets.""" 24 | 25 | import nibabel as nb 26 | import numpy as np 27 | from nilearn.image import load_img, threshold_img 28 | from nipype.interfaces.base import File, isdefined, traits 29 | from nipype.interfaces.mixins import reporting 30 | from nireports.reportlets.utils import compose_view, cuts_from_bbox 31 | from niworkflows import NIWORKFLOWS_LOG 32 | from niworkflows.utils.images import rotate_affine, rotation2canonical 33 | 34 | from ..viz.utils import coolwarm_transparent, plot_registration 35 | 36 | 37 | class _FieldmapReportletInputSpec(reporting.ReportCapableInputSpec): 38 | reference = File(exists=True, mandatory=True, desc='input reference') 39 | moving = File(exists=True, desc='input moving') 40 | fieldmap = File(exists=True, mandatory=True, desc='input fieldmap') 41 | max_alpha = traits.Float(0.7, usedefault=True, desc='maximum alpha channel') 42 | mask = File(exists=True, desc='brain mask') 43 | out_report = File('report.svg', usedefault=True, desc='filename for the visual report') 44 | show = traits.Enum(1, 0, 'both', usedefault=True, desc='where the fieldmap should be shown') 45 | reference_label = traits.Str( 46 | 'Reference', usedefault=True, desc='a label name for the reference mosaic' 47 | ) 48 | moving_label = traits.Str( 49 | 'Fieldmap (Hz)', usedefault=True, desc='a label name for the reference mosaic' 50 | ) 51 | apply_mask = traits.Bool(False, usedefault=True, desc='zero values outside mask') 52 | 53 | 54 | class FieldmapReportlet(reporting.ReportCapableInterface): 55 | """An abstract mixin to registration nipype interfaces.""" 56 | 57 | _n_cuts = 7 58 | input_spec = _FieldmapReportletInputSpec 59 | output_spec = reporting.ReportCapableOutputSpec 60 | 61 | def __init__(self, **kwargs): 62 | """Instantiate FieldmapReportlet.""" 63 | self._n_cuts = kwargs.pop('n_cuts', self._n_cuts) 64 | super(FieldmapReportlet, self).__init__(generate_report=True, **kwargs) 65 | 66 | def _run_interface(self, runtime): 67 | return runtime 68 | 69 | def _generate_report(self): 70 | """Generate a reportlet.""" 71 | NIWORKFLOWS_LOG.info('Generating visual report') 72 | 73 | movnii = load_img(self.inputs.reference) 74 | canonical_r = rotation2canonical(movnii) 75 | movnii = refnii = rotate_affine(movnii, rot=canonical_r) 76 | 77 | fmapnii = nb.squeeze_image(rotate_affine(load_img(self.inputs.fieldmap), rot=canonical_r)) 78 | 79 | if fmapnii.dataobj.ndim == 4: 80 | for i, tstep in enumerate(nb.four_to_three(fmapnii)): 81 | if np.any(np.asanyarray(tstep.dataobj) != 0): 82 | fmapnii = tstep 83 | break 84 | 85 | if isdefined(self.inputs.moving): 86 | movnii = rotate_affine(load_img(self.inputs.moving), rot=canonical_r) 87 | 88 | contour_nii = mask_nii = None 89 | if isdefined(self.inputs.mask): 90 | contour_nii = rotate_affine(load_img(self.inputs.mask), rot=canonical_r) 91 | maskdata = contour_nii.get_fdata() > 0 92 | else: 93 | mask_nii = threshold_img(refnii, 1e-3) 94 | maskdata = mask_nii.get_fdata() > 0 95 | cuts = cuts_from_bbox(contour_nii or mask_nii, cuts=self._n_cuts) 96 | fmapdata = fmapnii.get_fdata() 97 | vmax = max( 98 | abs(np.percentile(fmapdata[maskdata], 99.8)), 99 | abs(np.percentile(fmapdata[maskdata], 0.2)), 100 | ) 101 | if self.inputs.apply_mask: 102 | fmapdata[~maskdata] = 0 103 | fmapnii = fmapnii.__class__(fmapdata, fmapnii.affine, fmapnii.header) 104 | 105 | fmap_overlay = [ 106 | { 107 | 'overlay': fmapnii, 108 | 'overlay_params': { 109 | 'cmap': coolwarm_transparent(max_alpha=self.inputs.max_alpha), 110 | 'vmax': vmax, 111 | 'vmin': -vmax, 112 | }, 113 | } 114 | ] * 2 115 | 116 | if self.inputs.show != 'both': 117 | fmap_overlay[not self.inputs.show] = {} 118 | 119 | # Call composer 120 | compose_view( 121 | plot_registration( 122 | movnii, 123 | 'moving-image', 124 | estimate_brightness=True, 125 | cuts=cuts, 126 | label=self.inputs.moving_label, 127 | contour=contour_nii, 128 | compress=False, 129 | **fmap_overlay[1], 130 | ), 131 | plot_registration( 132 | refnii, 133 | 'fixed-image', 134 | estimate_brightness=True, 135 | cuts=cuts, 136 | label=self.inputs.reference_label, 137 | contour=contour_nii, 138 | compress=False, 139 | **fmap_overlay[0], 140 | ), 141 | out_file=self._out_report, 142 | ) 143 | -------------------------------------------------------------------------------- /sdcflows/interfaces/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/interfaces/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/interfaces/tests/test_epi.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2022 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test EPI interfaces.""" 24 | 25 | from pathlib import Path 26 | 27 | from ..epi import SortPEBlips 28 | 29 | 30 | def test_sort_pe_blips(tmpdir): 31 | tmpdir.chdir() 32 | 33 | input_comb = [('x-', 0.08), ('x-', 0.04), ('y-', 0.05), ('y', 0.05), ('x', 0.05)] 34 | 35 | fnames = [] 36 | for i in range(len(input_comb)): 37 | fnames.append(f'file{i}.nii') 38 | Path(fnames[-1]).write_text('') 39 | 40 | result = SortPEBlips( 41 | in_data=fnames, 42 | pe_dirs_fsl=[pe for pe, _ in input_comb], 43 | readout_times=[trt for _, trt in input_comb], 44 | ).run() 45 | 46 | assert result.outputs.out_data == [f'file{i}.nii' for i in (4, 3, 1, 0, 2)] 47 | -------------------------------------------------------------------------------- /sdcflows/interfaces/tests/test_fmap.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test fieldmap interfaces.""" 24 | 25 | import nibabel as nb 26 | import numpy as np 27 | import pytest 28 | 29 | from ..fmap import CheckB0Units 30 | 31 | 32 | @pytest.mark.parametrize('units', ('rad/s', 'Hz')) 33 | def test_units(tmpdir, units): 34 | """Check the conversion of units.""" 35 | tmpdir.chdir() 36 | hz = np.ones((5, 5, 5), dtype='float32') * 100 37 | data = hz.copy() 38 | 39 | if units == 'rad/s': 40 | data *= 2.0 * np.pi 41 | 42 | nb.Nifti1Image(data, np.eye(4), None).to_filename('data.nii.gz') 43 | out_data = nb.load( 44 | CheckB0Units(units=units, in_file='data.nii.gz').run().outputs.out_file 45 | ).get_fdata(dtype='float32') 46 | 47 | assert np.allclose(hz, out_data) 48 | -------------------------------------------------------------------------------- /sdcflows/interfaces/tests/test_reportlets.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test the fieldmap reportlets.""" 24 | 25 | from pathlib import Path 26 | 27 | import pytest 28 | 29 | from ...utils.epimanip import epi_mask 30 | from ..reportlets import FieldmapReportlet 31 | 32 | 33 | @pytest.mark.parametrize('mask', [True, False]) 34 | @pytest.mark.parametrize('apply_mask', [True, False]) 35 | def test_FieldmapReportlet(tmpdir, outdir, testdata_dir, mask, apply_mask): 36 | """Generate one reportlet.""" 37 | tmpdir.chdir() 38 | 39 | if not outdir: 40 | outdir = Path.cwd() 41 | 42 | report = FieldmapReportlet( 43 | reference=str(testdata_dir / 'epi.nii.gz'), 44 | moving=str(testdata_dir / 'epi.nii.gz'), 45 | fieldmap=str(testdata_dir / 'topup-field.nii.gz'), 46 | out_report=str(outdir / f'test-fieldmap{"-masked" * mask}{"-zeroed" * apply_mask}.svg'), 47 | apply_mask=apply_mask, 48 | ) 49 | 50 | if mask: 51 | report.inputs.mask = epi_mask(str(testdata_dir / 'epi.nii.gz')) 52 | 53 | report.run() 54 | -------------------------------------------------------------------------------- /sdcflows/interfaces/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test utilities.""" 24 | 25 | import nibabel as nb 26 | import numpy as np 27 | import pytest 28 | 29 | from ..utils import ConvertWarp, Deoblique, Flatten, PadSlices, Reoblique 30 | 31 | 32 | def test_Flatten(tmpdir): 33 | """Test the flattening interface.""" 34 | tmpdir.chdir() 35 | shape = (5, 5, 5) 36 | nb.Nifti1Image(np.zeros(shape), np.eye(4), None).to_filename('file1.nii.gz') 37 | nb.Nifti1Image(np.zeros((*shape, 6)), np.eye(4), None).to_filename('file2.nii.gz') 38 | nb.Nifti1Image(np.zeros((*shape, 2)), np.eye(4), None).to_filename('file3.nii.gz') 39 | 40 | out = Flatten( 41 | in_data=['file1.nii.gz', 'file2.nii.gz', 'file3.nii.gz'], 42 | in_meta=[{'a': 1}, {'b': 2}, {'c': 3}], 43 | max_trs=3, 44 | ).run() 45 | 46 | assert len(out.outputs.out_list) == 6 47 | 48 | out_meta = out.outputs.out_meta 49 | assert out_meta[0] == {'a': 1} 50 | assert out_meta[1] == out_meta[2] == out_meta[3] == {'b': 2} 51 | assert out_meta[4] == out_meta[5] == {'c': 3} 52 | 53 | 54 | @pytest.mark.parametrize('shape', [(10, 10, 10, 1, 3), (10, 10, 10, 3)]) 55 | def test_ConvertWarp(tmpdir, shape): 56 | """Exercise the interface.""" 57 | tmpdir.chdir() 58 | 59 | nb.Nifti1Image(np.zeros(shape, dtype='uint8'), np.eye(4), None).to_filename('3dQwarp.nii.gz') 60 | 61 | out = ConvertWarp(in_file='3dQwarp.nii.gz').run() 62 | 63 | nii = nb.load(out.outputs.out_file) 64 | assert nii.header.get_data_dtype() == np.float32 65 | assert nii.header.get_intent() == ('vector', (), '') 66 | assert nii.shape == (10, 10, 10, 1, 3) 67 | 68 | 69 | @pytest.mark.parametrize( 70 | 'angles,oblique', 71 | [ 72 | ((0, 0, 0), False), 73 | ((0.9, 0.001, 0.001), True), 74 | ((0, 0, 2 * np.pi), False), 75 | ], 76 | ) 77 | def test_Xeoblique(tmpdir, angles, oblique): 78 | """Exercise De/Reoblique interfaces.""" 79 | tmpdir.chdir() 80 | 81 | affine = nb.affines.from_matvec(nb.eulerangles.euler2mat(*angles)) 82 | nb.Nifti1Image(np.zeros((10, 10, 10), dtype='uint8'), affine, None).to_filename('epi.nii.gz') 83 | 84 | result = ( 85 | Deoblique( 86 | in_file='epi.nii.gz', 87 | in_mask='epi.nii.gz', 88 | ) 89 | .run() 90 | .outputs 91 | ) 92 | 93 | assert np.allclose(nb.load(result.out_file).affine, affine) is not oblique 94 | 95 | reoblique = ( 96 | Reoblique( 97 | in_plumb=result.out_file, 98 | in_field=result.out_file, 99 | in_epi='epi.nii.gz', 100 | ) 101 | .run() 102 | .outputs 103 | ) 104 | 105 | assert np.allclose(nb.load(reoblique.out_epi).affine, affine) 106 | 107 | 108 | @pytest.mark.parametrize( 109 | 'in_shape,expected_shape,padded', 110 | [ 111 | ((2, 2, 2), (2, 2, 2), False), 112 | ((2, 2, 3), (2, 2, 4), True), 113 | ((3, 3, 2, 2), (3, 3, 2, 2), False), 114 | ((3, 3, 3, 2), (3, 3, 4, 2), True), 115 | ], 116 | ) 117 | def test_pad_slices(tmpdir, in_shape, expected_shape, padded): 118 | tmpdir.chdir() 119 | 120 | data = np.random.rand(*in_shape) 121 | aff = np.eye(4) 122 | 123 | # RAS 124 | img = nb.Nifti1Image(data, aff) 125 | img.to_filename('epi-ras.nii.gz') 126 | res = PadSlices(in_file='epi-ras.nii.gz').run().outputs 127 | 128 | # LPS 129 | newaff = aff.copy() 130 | newaff[0, 0] *= -1.0 131 | newaff[1, 1] *= -1.0 132 | newaff[:2, 3] = aff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[:2] 133 | img2 = nb.Nifti1Image(np.flip(np.flip(data, 0), 1), newaff) 134 | img2.to_filename('epi-lps.nii.gz') 135 | res2 = PadSlices(in_file='epi-lps.nii.gz').run().outputs 136 | 137 | out_ras = nb.load(res.out_file) 138 | out_lps = nb.load(res2.out_file) 139 | assert out_ras.shape == out_lps.shape == expected_shape 140 | assert res.padded == padded 141 | -------------------------------------------------------------------------------- /sdcflows/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/dataset_description.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Test Dataset A, only empty files", 3 | "BIDSVersion": "", 4 | "License": "CC0", 5 | "Authors": ["Esteban O."], 6 | "Acknowledgements": "", 7 | "HowToAcknowledge": "", 8 | "Funding": "", 9 | "ReferencesAndLinks": [""], 10 | "DatasetDOI": "" 11 | } 12 | -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/anat/sub-01_FLAIR.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/anat/sub-01_FLAIR.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/anat/sub-01_T1w.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/anat/sub-01_T1w.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/anat/sub-01_T2w.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/anat/sub-01_T2w.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-AP_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-LR_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-PA_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-RL_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_acq-single_dir-PA_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005, 4 | "IntendedFor": [ 5 | "dwi/sub-01_dir-AP_dwi.nii.gz", 6 | "dwi/sub-01_dir-AP_sbref.nii.gz", 7 | "func/sub-01_task-rest_bold.nii.gz", 8 | "func/sub-01_task-rest_sbref.nii.gz" 9 | ] 10 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-AP_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-AP_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-AP_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-LR_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-LR_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-LR_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-PA_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-PA_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-PA_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-RL_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-RL_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_dir-RL_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_fieldmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "Units": "rad/s" 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_fieldmap.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_fieldmap.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_magnitude.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_magnitude.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_magnitude1.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_magnitude1.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_magnitude2.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_magnitude2.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phase1.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime": 0.005 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phase1.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phase1.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phase2.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime": 0.00746 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phase2.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phase2.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phasediff.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime1": 0.00500, 3 | "EchoTime2": 0.00746 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phasediff.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/fmap/sub-01_phasediff.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/func/sub-01_task-rest_bold.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/func/sub-01_task-rest_bold.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsA/sub-01/func/sub-01_task-rest_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsA/sub-01/func/sub-01_task-rest_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/dataset_description.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Test Dataset B, only empty files, with session", 3 | "BIDSVersion": "", 4 | "License": "CC0", 5 | "Authors": ["Esteban O."], 6 | "Acknowledgements": "", 7 | "HowToAcknowledge": "", 8 | "Funding": "", 9 | "ReferencesAndLinks": [""], 10 | "DatasetDOI": "" 11 | } 12 | -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/anat/sub-01_ses-1_FLAIR.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/anat/sub-01_ses-1_FLAIR.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/anat/sub-01_ses-1_T1w.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/anat/sub-01_ses-1_T1w.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/anat/sub-01_ses-1_T2w.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/anat/sub-01_ses-1_T2w.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-AP_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-AP_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-AP_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-AP_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-AP_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-LR_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-LR_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-LR_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-LR_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-LR_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-PA_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-PA_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-PA_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-PA_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-PA_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-RL_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-RL_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-RL_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-RL_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/dwi/sub-01_ses-1_dir-RL_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_acq-single_dir-PA_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005, 4 | "IntendedFor": [ 5 | "ses-1/dwi/sub-01_ses-1_dir-AP_dwi.nii.gz", 6 | "ses-1/dwi/sub-01_ses-1_dir-AP_sbref.nii.gz", 7 | "ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz", 8 | "ses-1/func/sub-01_ses-1_task-rest_sbref.nii.gz" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_acq-single_dir-PA_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_acq-single_dir-PA_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-AP_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-AP_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-AP_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-LR_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-LR_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-LR_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-PA_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-PA_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-PA_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-RL_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-RL_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_dir-RL_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_fieldmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "Units": "rad/s" 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_fieldmap.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_fieldmap.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_magnitude.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_magnitude.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_magnitude1.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_magnitude1.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_magnitude2.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_magnitude2.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phase1.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime": 0.005 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phase1.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phase1.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phase2.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime": 0.00746 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phase2.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phase2.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phasediff.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime1": 0.00500, 3 | "EchoTime2": 0.00746 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phasediff.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/fmap/sub-01_ses-1_phasediff.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsB/sub-01/ses-1/func/sub-01_ses-1_task-rest_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsB/sub-01/ses-1/func/sub-01_ses-1_task-rest_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/dataset_description.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Test Dataset A, only empty files", 3 | "BIDSVersion": "", 4 | "License": "CC0", 5 | "Authors": ["Esteban O."], 6 | "Acknowledgements": "", 7 | "HowToAcknowledge": "", 8 | "Funding": "", 9 | "ReferencesAndLinks": [""], 10 | "DatasetDOI": "" 11 | } 12 | -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/anat/sub-01_FLAIR.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/anat/sub-01_FLAIR.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/anat/sub-01_T1w.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/anat/sub-01_T1w.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/anat/sub-01_T2w.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/anat/sub-01_T2w.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "i-", 3 | "TotalReadoutTime": 0.005 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhaseEncodingDirection": "j", 3 | "TotalReadoutTime": 0.005, 4 | "IntendedFor": [ 5 | "dwi/sub-01_dir-AP_dwi.nii.gz", 6 | "dwi/sub-01_dir-AP_sbref.nii.gz", 7 | "func/sub-01_task-rest_bold.nii.gz", 8 | "func/sub-01_task-rest_sbref.nii.gz" 9 | ] 10 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "B0FieldIdentifier": "pepolar4pe", 3 | "PhaseEncodingDirection": "j-", 4 | "TotalReadoutTime": 0.005 5 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "B0FieldIdentifier": "pepolar4pe", 3 | "PhaseEncodingDirection": "i", 4 | "TotalReadoutTime": 0.005 5 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "B0FieldIdentifier": "pepolar4pe", 3 | "PhaseEncodingDirection": "j", 4 | "TotalReadoutTime": 0.005 5 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.json: -------------------------------------------------------------------------------- 1 | { 2 | "B0FieldIdentifier": "pepolar4pe", 3 | "PhaseEncodingDirection": "i-", 4 | "TotalReadoutTime": 0.005 5 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "Units": "rad/s" 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude1.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude1.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude2.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude2.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime": 0.005 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime": 0.00746 3 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoTime1": 0.00500, 3 | "EchoTime2": 0.00746 4 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_bold.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_bold.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_sbref.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_sbref.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/dsC/task-rest_bold.json: -------------------------------------------------------------------------------- 1 | { 2 | "TaskName": "Rest", 3 | "PhaseEncodingDirection": "j-", 4 | "TotalReadoutTime": 0.05 5 | } -------------------------------------------------------------------------------- /sdcflows/tests/data/epi.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/epi.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/epi2fmap_xfm.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/epi2fmap_xfm.txt -------------------------------------------------------------------------------- /sdcflows/tests/data/field-coeff-tests.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/field-coeff-tests.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/fmap2epi_xfm.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/fmap2epi_xfm.txt -------------------------------------------------------------------------------- /sdcflows/tests/data/topup-coeff-fixed.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/topup-coeff-fixed.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/topup-coeff.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/topup-coeff.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/data/topup-field.nii.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/tests/data/topup-field.nii.gz -------------------------------------------------------------------------------- /sdcflows/tests/test_version.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test _version.py.""" 24 | 25 | import sys 26 | from importlib import reload 27 | from importlib.metadata import PackageNotFoundError 28 | 29 | import sdcflows 30 | 31 | 32 | def test_version_scm0(monkeypatch): 33 | """Retrieve the version via setuptools_scm.""" 34 | 35 | class _version: 36 | __version__ = '10.0.0' 37 | 38 | monkeypatch.setitem(sys.modules, 'sdcflows._version', _version) 39 | reload(sdcflows) 40 | assert sdcflows.__version__ == '10.0.0' 41 | 42 | 43 | def test_version_scm1(monkeypatch): 44 | """Retrieve the version via importlib.metadata.""" 45 | monkeypatch.setitem(sys.modules, 'sdcflows._version', None) 46 | 47 | def _version(name): 48 | return '9.0.0' 49 | 50 | monkeypatch.setattr('importlib.metadata.version', _version) 51 | reload(sdcflows) 52 | assert sdcflows.__version__ == '9.0.0' 53 | 54 | 55 | def test_version_scm2(monkeypatch): 56 | """Check version could not be interpolated.""" 57 | monkeypatch.setitem(sys.modules, 'sdcflows._version', None) 58 | 59 | def _raise(name): 60 | raise PackageNotFoundError('No get_distribution mock') 61 | 62 | monkeypatch.setattr('importlib.metadata.version', _raise) 63 | reload(sdcflows) 64 | assert sdcflows.__version__ == '0+unknown' 65 | -------------------------------------------------------------------------------- /sdcflows/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/utils/__init__.py -------------------------------------------------------------------------------- /sdcflows/utils/misc.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Basic miscellaneous utilities.""" 24 | 25 | import logging 26 | 27 | 28 | def front(inlist): 29 | """ 30 | Pop from a list or tuple, otherwise return untouched. 31 | 32 | Examples 33 | -------- 34 | >>> front([1, 0]) 35 | 1 36 | 37 | >>> front("/path/somewhere") 38 | '/path/somewhere' 39 | 40 | """ 41 | if isinstance(inlist, (list, tuple)): 42 | return inlist[0] 43 | return inlist 44 | 45 | 46 | def last(inlist): 47 | """ 48 | Return the last element from a list or tuple, otherwise return untouched. 49 | 50 | Examples 51 | -------- 52 | >>> last([1, 0]) 53 | 0 54 | 55 | >>> last("/path/somewhere") 56 | '/path/somewhere' 57 | 58 | """ 59 | if isinstance(inlist, (list, tuple)): 60 | return inlist[-1] 61 | return inlist 62 | 63 | 64 | def get_free_mem(): 65 | """Probe the free memory right now.""" 66 | try: 67 | from psutil import virtual_memory 68 | 69 | return round(virtual_memory().free, 1) 70 | except Exception: 71 | return None 72 | 73 | 74 | def create_logger(name: str, level: int = 40) -> logging.Logger: 75 | logger = logging.getLogger(name) 76 | logger.setLevel(level) 77 | # clear any existing handlers 78 | logger.handlers.clear() 79 | handler = logging.StreamHandler() 80 | handler.setLevel(level) 81 | # formatter = logging.Formatter('[%(name)s %(asctime)s] - %(levelname)s: %(message)s') 82 | formatter = logging.Formatter('[%(name)s - %(levelname)s]: %(message)s') 83 | handler.setFormatter(formatter) 84 | logger.addHandler(handler) 85 | return logger 86 | -------------------------------------------------------------------------------- /sdcflows/utils/phasemanip.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Utilities to manipulate phase and phase difference maps.""" 24 | 25 | 26 | def au2rads(in_file, newpath=None): 27 | """Convert the input phase map in arbitrary units (a.u.) to rads.""" 28 | import nibabel as nb 29 | import numpy as np 30 | from nipype.utils.filemanip import fname_presuffix 31 | 32 | im = nb.load(in_file) 33 | data = im.get_fdata(caching='unchanged') # Read as float64 for safety 34 | hdr = im.header.copy() 35 | 36 | # Rescale to [0, 2*pi] 37 | data = (data - data.min()) * (2 * np.pi / (data.max() - data.min())) 38 | 39 | # Round to float32 and clip 40 | data = np.clip(np.float32(data), 0.0, 2 * np.pi) 41 | 42 | hdr.set_data_dtype(np.float32) 43 | hdr.set_xyzt_units('mm') 44 | out_file = fname_presuffix(str(in_file), suffix='_rads', newpath=newpath) 45 | nb.Nifti1Image(data, None, hdr).to_filename(out_file) 46 | return out_file 47 | 48 | 49 | def subtract_phases(in_phases, in_meta, newpath=None): 50 | """Calculate the phase-difference map, given two input phase maps.""" 51 | import nibabel as nb 52 | import numpy as np 53 | from nipype.utils.filemanip import fname_presuffix 54 | 55 | echo_times = tuple([m.pop('EchoTime', None) for m in in_meta]) 56 | if echo_times[0] > echo_times[1]: 57 | in_phases = (in_phases[1], in_phases[0]) 58 | in_meta = (in_meta[1], in_meta[0]) 59 | echo_times = (echo_times[1], echo_times[0]) 60 | 61 | in_phases_nii = [nb.load(ph) for ph in in_phases] 62 | sub_data = in_phases_nii[1].get_fdata(dtype='float32') - in_phases_nii[0].get_fdata( 63 | dtype='float32' 64 | ) 65 | 66 | # wrap negative radians back to [0, 2pi] 67 | sub_data[sub_data < 0] += 2 * np.pi 68 | sub_data = np.clip(sub_data, 0.0, 2 * np.pi) 69 | 70 | new_meta = in_meta[1].copy() 71 | new_meta.update(in_meta[0]) 72 | new_meta['EchoTime1'] = echo_times[0] 73 | new_meta['EchoTime2'] = echo_times[1] 74 | 75 | hdr = in_phases_nii[0].header.copy() 76 | hdr.set_data_dtype(np.float32) 77 | hdr.set_xyzt_units('mm') 78 | nii = nb.Nifti1Image(sub_data, in_phases_nii[0].affine, hdr) 79 | out_phdiff = fname_presuffix(in_phases[0], suffix='_phdiff', newpath=newpath) 80 | nii.to_filename(out_phdiff) 81 | return out_phdiff, new_meta 82 | 83 | 84 | def phdiff2fmap(in_file, delta_te, newpath=None): 85 | """Convert the input phase-difference map into a *fieldmap* in Hz.""" 86 | import nibabel as nb 87 | import numpy as np 88 | from nipype.utils.filemanip import fname_presuffix 89 | 90 | out_file = fname_presuffix(str(in_file), suffix='_fmap', newpath=newpath) 91 | image = nb.load(in_file) 92 | data = image.get_fdata(dtype='float32') / (2.0 * np.pi * delta_te) 93 | nii = nb.Nifti1Image(data, image.affine, image.header) 94 | nii.set_data_dtype(np.float32) 95 | nii.to_filename(out_file) 96 | return out_file 97 | 98 | 99 | def delta_te(in_values): 100 | r""" 101 | Read :math:`\Delta_\text{TE}` from BIDS metadata dict. 102 | 103 | Examples 104 | -------- 105 | >>> t = delta_te({"EchoTime1": 0.00522, "EchoTime2": 0.00768}) 106 | >>> f"{t:.5f}" 107 | '0.00246' 108 | 109 | >>> t = delta_te({'EchoTimeDifference': 0.00246}) 110 | >>> f"{t:.5f}" 111 | '0.00246' 112 | 113 | >>> delta_te({"EchoTime1": "a"}) # doctest: +IGNORE_EXCEPTION_DETAIL 114 | Traceback (most recent call last): 115 | ValueError: 116 | 117 | >>> delta_te({"EchoTime2": "a"}) # doctest: +IGNORE_EXCEPTION_DETAIL 118 | Traceback (most recent call last): 119 | ValueError: 120 | 121 | >>> delta_te({}) # doctest: +IGNORE_EXCEPTION_DETAIL 122 | Traceback (most recent call last): 123 | ValueError: 124 | 125 | >>> delta_te({"EchoTimeDifference": "a"}) # doctest: +IGNORE_EXCEPTION_DETAIL 126 | Traceback (most recent call last): 127 | ValueError: 128 | 129 | """ 130 | te2 = in_values.get('EchoTime2') 131 | te1 = in_values.get('EchoTime1') 132 | 133 | if te2 is None and te1 is None: 134 | try: 135 | te2 = float(in_values.get('EchoTimeDifference')) 136 | return abs(te2) 137 | except TypeError: 138 | raise ValueError('Phase/phase-difference fieldmaps: no echo-times information.') 139 | except ValueError: 140 | raise ValueError(f'Could not interpret metadata .') 141 | try: 142 | te2 = float(te2 or 'unknown') 143 | te1 = float(te1 or 'unknown') 144 | except ValueError: 145 | raise ValueError(f'Could not interpret metadata .') 146 | 147 | return abs(te2 - te1) 148 | -------------------------------------------------------------------------------- /sdcflows/utils/telemetry.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2023 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | # STATEMENT OF CHANGES: This file is derived from sources licensed under the Apache-2.0 terms, 24 | # and this file has been changed. 25 | # The original file this work derives from is found at: 26 | # https://github.com/nipreps/mriqc/blob/8ceadba8669cc2a86119a97b9311ab968f11c6eb/mriqc/utils/telemetry.py 27 | """Ping MIGAS for telemetry.""" 28 | 29 | import migas 30 | 31 | from sdcflows import config 32 | 33 | MIGAS_PACKAGE = 'nipreps/sdcflows' 34 | 35 | 36 | def setup_migas(init_ping: bool = True, exit_ping: bool = True) -> None: 37 | """ 38 | Prepare the migas python client to communicate with a migas server. 39 | If ``init`` is ``True``, send an initial breadcrumb. 40 | """ 41 | # generate session UUID from generated run UUID 42 | session_id = None 43 | if config.execution.run_uuid: 44 | session_id = config.execution.run_uuid.split('_', 1)[-1] 45 | 46 | migas.setup(session_id=session_id) 47 | if init_ping: 48 | # send initial status ping 49 | send_crumb(status='R', status_desc='workflow start') 50 | if exit_ping: 51 | from migas.error.nipype import node_execution_error 52 | 53 | migas.track_exit( 54 | MIGAS_PACKAGE, 55 | config.environment.version, 56 | {'NodeExecutionError': node_execution_error}, 57 | ) 58 | 59 | 60 | def send_crumb(**kwargs) -> dict: 61 | """ 62 | Communicate with the migas telemetry server. This requires `migas.setup()` to be called. 63 | """ 64 | return migas.add_breadcrumb(MIGAS_PACKAGE, config.environment.version, **kwargs) 65 | -------------------------------------------------------------------------------- /sdcflows/utils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/utils/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/utils/tests/test_misc.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test miscellaneous utilities.""" 24 | 25 | import sys 26 | import types 27 | from collections import namedtuple 28 | 29 | import pytest 30 | 31 | from ..misc import get_free_mem 32 | 33 | 34 | @pytest.mark.parametrize('retval', [None, 10]) 35 | def test_get_free_mem(monkeypatch, retval): 36 | """Test the get_free_mem utility.""" 37 | 38 | def mock_func(): 39 | if retval is None: 40 | raise ImportError 41 | return namedtuple('Mem', ('free',))(free=retval) 42 | 43 | psutil = types.ModuleType('psutil') 44 | psutil.virtual_memory = mock_func 45 | monkeypatch.setitem(sys.modules, 'psutil', psutil) 46 | assert get_free_mem() == retval 47 | -------------------------------------------------------------------------------- /sdcflows/utils/tests/test_phasemanip.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test phase manipulation routines.""" 24 | 25 | import nibabel as nb 26 | import numpy as np 27 | 28 | from ..phasemanip import au2rads, phdiff2fmap 29 | 30 | 31 | def test_au2rads(tmp_path): 32 | """Check the conversion.""" 33 | data = np.random.randint(0, high=4096, size=(5, 5, 5)) 34 | data[0, 0, 0] = 0 35 | data[-1, -1, -1] = 4096 36 | 37 | nb.Nifti1Image(data.astype('int16'), np.eye(4)).to_filename(tmp_path / 'testdata.nii.gz') 38 | 39 | out_file = au2rads(tmp_path / 'testdata.nii.gz') 40 | 41 | assert np.allclose( 42 | (data / 4096).astype('float32') * 2.0 * np.pi, 43 | nb.load(out_file).get_fdata(dtype='float32'), 44 | ) 45 | 46 | 47 | def test_phdiff2fmap(tmp_path): 48 | """Check the conversion.""" 49 | nb.Nifti1Image( 50 | np.ones((5, 5, 5), dtype='float32') * 2.0 * np.pi * 2.46e-3, np.eye(4) 51 | ).to_filename(tmp_path / 'testdata.nii.gz') 52 | 53 | out_file = phdiff2fmap(tmp_path / 'testdata.nii.gz', 2.46e-3) 54 | 55 | assert np.allclose(np.ones((5, 5, 5)), nb.load(out_file).get_fdata(dtype='float32')) 56 | -------------------------------------------------------------------------------- /sdcflows/utils/tests/test_tools.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test EPI manipulation routines.""" 24 | 25 | import nibabel as nb 26 | import numpy as np 27 | import pytest 28 | from nitransforms.linear import Affine 29 | 30 | from sdcflows.utils.tools import brain_masker, deoblique_and_zooms 31 | 32 | 33 | def test_epi_mask(tmpdir, testdata_dir): 34 | """Check mask algorithm.""" 35 | tmpdir.chdir() 36 | mask = brain_masker(testdata_dir / 'epi.nii.gz')[-1] 37 | assert abs(np.asanyarray(nb.load(mask).dataobj).sum() - 166476) < 10 38 | 39 | 40 | @pytest.mark.parametrize('padding', [0, 1, 4]) 41 | @pytest.mark.parametrize('factor', [1, 4, 0.8]) 42 | @pytest.mark.parametrize('centered', [True, False]) 43 | @pytest.mark.parametrize( 44 | 'rotate', 45 | [ 46 | np.eye(4), 47 | # Rotate 90 degrees around x-axis 48 | np.array([[0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 0], [0, 0, 0, 1]]), 49 | # Rotate 30 degrees around x-axis 50 | nb.affines.from_matvec( 51 | nb.eulerangles.euler2mat(0, 0, 30 * np.pi / 180), 52 | (0, 0, 0), 53 | ), 54 | # Rotate 48 degrees around y-axis and translation 55 | nb.affines.from_matvec( 56 | nb.eulerangles.euler2mat(0, 48 * np.pi / 180, 0), 57 | (2.0, 1.2, 0.7), 58 | ), 59 | ], 60 | ) 61 | def test_deoblique_and_zooms(tmpdir, padding, factor, centered, rotate, debug=False): 62 | """Check deoblique and denser.""" 63 | tmpdir.chdir() 64 | 65 | # Generate an example reference image 66 | ref_shape = (80, 128, 160) if factor < 1 else (20, 32, 40) 67 | ref_data = np.zeros(ref_shape, dtype=np.float32) 68 | ref_data[1:-2, 10:-11, 1:-2] = 1 69 | ref_affine = np.diag([2.0, 1.25, 1.0, 1.0]) 70 | ref_zooms = nb.affines.voxel_sizes(ref_affine) 71 | if centered: 72 | ref_affine[:3, 3] -= nb.affines.apply_affine( 73 | ref_affine, 74 | 0.5 * (np.array(ref_data.shape) - 1), 75 | ) 76 | ref_img = nb.Nifti1Image(ref_data, ref_affine) 77 | ref_img.header.set_qform(ref_affine, 1) 78 | ref_img.header.set_sform(ref_affine, 0) 79 | 80 | # Generate an example oblique image 81 | mov_affine = rotate @ ref_affine 82 | mov_img = nb.Nifti1Image(ref_data, mov_affine) 83 | mov_img.header.set_qform(mov_affine, 1) 84 | mov_img.header.set_sform(mov_affine, 0) 85 | 86 | # Call function with default parameters 87 | out_img = deoblique_and_zooms(ref_img, mov_img, padding=padding, factor=factor) 88 | 89 | # Check output shape and zooms 90 | assert np.allclose(out_img.header.get_zooms()[:3], ref_zooms / factor) 91 | 92 | # Check idempotency with a lot of tolerance 93 | ref_resampled = Affine(reference=out_img).apply(ref_img, order=0) 94 | ref_back = Affine(reference=ref_img).apply(ref_resampled, order=0) 95 | resampled = Affine(reference=out_img).apply(mov_img, order=2) 96 | if debug: 97 | ref_img.to_filename('reference.nii.gz') 98 | ref_back.to_filename('reference_back.nii.gz') 99 | ref_resampled.to_filename('reference_resampled.nii.gz') 100 | mov_img.to_filename('moving.nii.gz') 101 | resampled.to_filename('resampled.nii.gz') 102 | 103 | # Allow up to 3% pixels wrong after up(down)sampling and walk back. 104 | assert np.abs(np.clip(ref_back.get_fdata(), 0, 1) - ref_data).sum() < ref_data.size * 0.03 105 | 106 | vox_vol_out = np.prod(out_img.header.get_zooms()) 107 | vox_vol_mov = np.prod(mov_img.header.get_zooms()) 108 | vol_factor = vox_vol_out / vox_vol_mov 109 | 110 | ref_volume = ref_data.sum() 111 | res_volume = np.clip(resampled.get_fdata(), 0, 1).sum() * vol_factor 112 | # Tolerate up to 2% variation of the volume of the moving image 113 | assert abs(1 - res_volume / ref_volume) < 0.02 114 | -------------------------------------------------------------------------------- /sdcflows/viz/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/viz/__init__.py -------------------------------------------------------------------------------- /sdcflows/viz/utils.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Visualization tooling.""" 24 | 25 | 26 | def plot_registration( 27 | anat_nii, 28 | div_id, 29 | plot_params=None, 30 | order=('z', 'x', 'y'), 31 | cuts=None, 32 | estimate_brightness=False, 33 | label=None, 34 | contour=None, 35 | compress='auto', 36 | overlay=None, 37 | overlay_params=None, 38 | ): 39 | """ 40 | Plot the foreground and background views. 41 | 42 | Default order is: axial, coronal, sagittal 43 | """ 44 | from uuid import uuid4 45 | 46 | import matplotlib.pyplot as plt 47 | from lxml import etree 48 | from nilearn.plotting import plot_anat 49 | from nireports._vendored.svgutils.transform import SVGFigure 50 | from nireports.reportlets.utils import SVGNS, extract_svg, robust_set_limits 51 | 52 | plot_params = plot_params or {} 53 | 54 | # Use default MNI cuts if none defined 55 | if cuts is None: 56 | raise NotImplementedError # TODO 57 | 58 | out_files = [] 59 | if estimate_brightness: 60 | plot_params = robust_set_limits( 61 | anat_nii.get_fdata(dtype='float32').reshape(-1), plot_params 62 | ) 63 | 64 | # Plot each cut axis 65 | for i, mode in enumerate(list(order)): 66 | plot_params['display_mode'] = mode 67 | plot_params['cut_coords'] = cuts[mode] 68 | if i == 0: 69 | plot_params['title'] = label 70 | else: 71 | plot_params['title'] = None 72 | 73 | # Generate nilearn figure 74 | display = plot_anat(anat_nii, **plot_params) 75 | if overlay is not None: 76 | _overlay_params = { 77 | 'vmin': overlay.get_fdata(dtype='float32').min(), 78 | 'vmax': overlay.get_fdata(dtype='float32').max(), 79 | 'cmap': plt.cm.gray, 80 | 'interpolation': 'nearest', 81 | } 82 | _overlay_params.update(overlay_params) 83 | display.add_overlay(overlay, **_overlay_params) 84 | if contour is not None: 85 | display.add_contours(contour, colors='g', levels=[0.5], linewidths=0.5) 86 | 87 | svg = extract_svg(display, compress=compress) 88 | display.close() 89 | 90 | # Find and replace the figure_1 id. 91 | xml_data = etree.fromstring(svg) 92 | find_text = etree.ETXPath("//{%s}g[@id='figure_1']" % SVGNS) 93 | find_text(xml_data)[0].set('id', '%s-%s-%s' % (div_id, mode, uuid4())) 94 | 95 | svg_fig = SVGFigure() 96 | svg_fig.root = xml_data 97 | out_files.append(svg_fig) 98 | 99 | return out_files 100 | 101 | 102 | def coolwarm_transparent(max_alpha=0.7, opaque_perc=30, transparent_perc=8): 103 | """Modify the coolwarm color scale to have full transparency around the middle.""" 104 | import matplotlib.pylab as pl 105 | import numpy as np 106 | from matplotlib.colors import ListedColormap 107 | 108 | # Choose colormap 109 | cmap = pl.cm.coolwarm 110 | 111 | # Get the colormap colors 112 | my_cmap = cmap(np.arange(cmap.N)) 113 | 114 | _20perc = (cmap.N * opaque_perc) // 100 115 | midpoint = cmap.N // 2 + 1 116 | _10perc = (cmap.N * transparent_perc) // 100 117 | # Set alpha 118 | alpha = np.zeros(cmap.N) 119 | alpha[:_20perc] = max_alpha 120 | alpha[-_20perc:] = max_alpha 121 | alpha[_20perc : midpoint - _10perc - 1] = np.linspace( 122 | max_alpha, 0, len(alpha[_20perc : midpoint - _10perc - 1]) 123 | ) 124 | alpha[midpoint + _10perc : -_20perc] = np.linspace( 125 | 0, max_alpha, len(alpha[midpoint + _10perc : -_20perc]) 126 | ) 127 | my_cmap[:, -1] = alpha 128 | return ListedColormap(my_cmap) 129 | -------------------------------------------------------------------------------- /sdcflows/workflows/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/workflows/__init__.py -------------------------------------------------------------------------------- /sdcflows/workflows/ancillary.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Estimate fieldmaps for :abbr:`SDC (susceptibility distortion correction)`.""" 24 | 25 | from nipype import logging 26 | from nipype.interfaces import utility as niu 27 | from nipype.pipeline import engine as pe 28 | from niworkflows.engine.workflows import LiterateWorkflow as Workflow 29 | 30 | LOGGER = logging.getLogger('nipype.workflow') 31 | 32 | 33 | def init_brainextraction_wf(name='brainextraction_wf'): 34 | """ 35 | Remove nonbrain tissue from images. 36 | 37 | Parameters 38 | ---------- 39 | name : :obj:`str`, optional 40 | Workflow name (default: ``"brainextraction_wf"``) 41 | 42 | Inputs 43 | ------ 44 | in_file : :obj:`str` 45 | the GRE magnitude or EPI reference to be brain-extracted 46 | bspline_dist : :obj:`int`, optional 47 | Integer to replace default distance of b-spline separation for N4 48 | 49 | Outputs 50 | ------- 51 | out_file : :obj:`str` 52 | the input file after N4 and smart clipping 53 | out_brain : :obj:`str` 54 | the output file, just the brain extracted 55 | out_mask : :obj:`str` 56 | the calculated mask 57 | out_probseg : :obj:`str` 58 | a probability map that the random walker reached 59 | a given voxel (some sort of "soft" brainmask) 60 | 61 | """ 62 | from nipype.interfaces.ants import N4BiasFieldCorrection 63 | from niworkflows.interfaces.nibabel import IntensityClip 64 | 65 | from ..interfaces.brainmask import BrainExtraction 66 | 67 | wf = Workflow(name=name) 68 | 69 | inputnode = pe.Node( 70 | niu.IdentityInterface(fields=('in_file', 'bspline_dist')), name='inputnode' 71 | ) 72 | outputnode = pe.Node( 73 | niu.IdentityInterface( 74 | fields=( 75 | 'out_file', 76 | 'out_brain', 77 | 'out_mask', 78 | 'out_probseg', 79 | ) 80 | ), 81 | name='outputnode', 82 | ) 83 | clipper_pre = pe.Node(IntensityClip(), name='clipper_pre') 84 | 85 | # de-gradient the fields ("bias/illumination artifact") 86 | n4 = pe.Node( 87 | N4BiasFieldCorrection( 88 | dimension=3, 89 | copy_header=True, 90 | n_iterations=[50] * 5, 91 | convergence_threshold=1e-7, 92 | shrink_factor=4, 93 | ), 94 | n_procs=8, 95 | name='n4', 96 | ) 97 | clipper_post = pe.Node(IntensityClip(p_min=0.01, p_max=99.9), name='clipper_post') 98 | masker = pe.Node(BrainExtraction(), name='masker') 99 | 100 | # fmt:off 101 | wf.connect([ 102 | (inputnode, clipper_pre, [("in_file", "in_file")]), 103 | (inputnode, n4, [("bspline_dist", "bspline_fitting_distance")]), 104 | (clipper_pre, n4, [("out_file", "input_image")]), 105 | (n4, clipper_post, [("output_image", "in_file")]), 106 | (clipper_post, masker, [("out_file", "in_file")]), 107 | (clipper_post, outputnode, [("out_file", "out_file")]), 108 | (masker, outputnode, [("out_file", "out_brain"), 109 | ("out_mask", "out_mask"), 110 | ("out_probseg", "out_probseg")]), 111 | ]) 112 | # fmt:on 113 | 114 | return wf 115 | -------------------------------------------------------------------------------- /sdcflows/workflows/apply/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/workflows/apply/__init__.py -------------------------------------------------------------------------------- /sdcflows/workflows/apply/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/workflows/apply/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/workflows/apply/tests/test_correction.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test unwarp.""" 24 | 25 | import json 26 | 27 | import pytest 28 | from nipype.interfaces import utility as niu 29 | from nipype.pipeline import engine as pe 30 | from nireports.interfaces.reporting.base import SimpleBeforeAfterRPT as SimpleBeforeAfter 31 | 32 | from sdcflows.workflows.apply.correction import init_unwarp_wf 33 | 34 | 35 | @pytest.mark.parametrize('with_affine', [False, True]) 36 | def test_unwarp_wf(tmpdir, datadir, workdir, outdir, with_affine): 37 | """Test the unwarping workflow.""" 38 | tmpdir.chdir() 39 | 40 | derivs_path = datadir / 'HCP101006' / 'derivatives' / 'sdcflows-2.x' 41 | 42 | distorted = ( 43 | datadir / 'HCP101006' / 'sub-101006' / 'func' / 'sub-101006_task-rest_dir-LR_sbref.nii.gz' 44 | ) 45 | 46 | workflow = init_unwarp_wf(omp_nthreads=2, debug=True) 47 | workflow.inputs.inputnode.distorted = str(distorted) 48 | workflow.inputs.inputnode.metadata = json.loads( 49 | (distorted.parent / distorted.name.replace('.nii.gz', '.json')).read_text() 50 | ) 51 | workflow.inputs.inputnode.fmap_coeff = [ 52 | str(derivs_path / 'sub-101006_coeff-1_desc-topup_fieldmap.nii.gz') 53 | ] 54 | 55 | if with_affine: 56 | workflow.inputs.inputnode.fmap2data_xfm = str( 57 | str(derivs_path / 'sub-101006_from-sbrefLR_to-fieldmapref_mode-image_xfm.mat') 58 | ) 59 | 60 | if outdir: 61 | from ....interfaces.reportlets import FieldmapReportlet 62 | from ...outputs import DerivativesDataSink 63 | 64 | outdir = outdir / f'with{"" if with_affine else "out"}-affine' 65 | outdir.mkdir(exist_ok=True, parents=True) 66 | unwarp_wf = workflow # Change variable name 67 | workflow = pe.Workflow(name='outputs_unwarp_wf') 68 | squeeze = pe.Node(niu.Function(function=_squeeze), name='squeeze') 69 | 70 | report = pe.Node( 71 | SimpleBeforeAfter( 72 | before_label='Distorted', after_label='Corrected', before=str(distorted) 73 | ), 74 | name='report', 75 | mem_gb=0.1, 76 | ) 77 | ds_report = pe.Node( 78 | DerivativesDataSink( 79 | base_directory=str(outdir), 80 | suffix='bold', 81 | desc='corrected', 82 | datatype='figures', 83 | dismiss_entities=('fmap',), 84 | source_file=distorted, 85 | ), 86 | name='ds_report', 87 | run_without_submitting=True, 88 | ) 89 | 90 | rep = pe.Node(FieldmapReportlet(), 'simple_report') 91 | rep.interface._always_run = True 92 | 93 | ds_fmap_report = pe.Node( 94 | DerivativesDataSink( 95 | base_directory=str(outdir), 96 | datatype='figures', 97 | suffix='bold', 98 | desc='fieldmap', 99 | dismiss_entities=('fmap',), 100 | source_file=distorted, 101 | ), 102 | name='ds_fmap_report', 103 | ) 104 | 105 | # fmt: off 106 | workflow.connect([ 107 | (unwarp_wf, squeeze, [("outputnode.corrected", "in_file")]), 108 | (unwarp_wf, report, [("outputnode.corrected_mask", "wm_seg")]), 109 | (squeeze, report, [("out", "after")]), 110 | (report, ds_report, [("out_report", "in_file")]), 111 | (squeeze, rep, [("out", "reference")]), 112 | (unwarp_wf, rep, [ 113 | ("outputnode.fieldmap", "fieldmap"), 114 | ("outputnode.corrected_mask", "mask"), 115 | ]), 116 | (rep, ds_fmap_report, [("out_report", "in_file")]), 117 | ]) 118 | # fmt: on 119 | 120 | if workdir: 121 | workflow.base_dir = str(workdir) 122 | workflow.run(plugin='Linear') 123 | 124 | 125 | def _squeeze(in_file): 126 | from pathlib import Path 127 | 128 | import nibabel as nb 129 | 130 | img = nb.load(in_file) 131 | squeezed = nb.squeeze_image(img) 132 | 133 | if squeezed.shape == img.shape: 134 | return in_file 135 | 136 | out_fname = Path.cwd() / Path(in_file).name 137 | squeezed.to_filename(out_fname) 138 | return str(out_fname) 139 | -------------------------------------------------------------------------------- /sdcflows/workflows/apply/tests/test_registration.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test pepolar type of fieldmaps.""" 24 | 25 | import os 26 | 27 | import pytest 28 | from nipype.interfaces import utility as niu 29 | from nipype.pipeline import engine as pe 30 | from nireports.interfaces.reporting.base import SimpleBeforeAfterRPT as SimpleBeforeAfter 31 | 32 | from ...fit.fieldmap import init_magnitude_wf 33 | from ..registration import init_coeff2epi_wf 34 | 35 | 36 | @pytest.mark.skipif(os.getenv('TRAVIS') == 'true', reason='this is TravisCI') 37 | def test_registration_wf(tmpdir, datadir, workdir, outdir): 38 | """Test fieldmap-to-target alignment workflow.""" 39 | epi_ref_wf = init_magnitude_wf(2, name='epi_ref_wf') 40 | epi_ref_wf.inputs.inputnode.magnitude = ( 41 | datadir / 'HCP101006' / 'sub-101006' / 'func' / 'sub-101006_task-rest_dir-LR_sbref.nii.gz' 42 | ) 43 | 44 | magnitude = datadir / 'HCP101006' / 'sub-101006' / 'fmap' / 'sub-101006_magnitude1.nii.gz' 45 | fmap_ref_wf = init_magnitude_wf(2, name='fmap_ref_wf') 46 | fmap_ref_wf.inputs.inputnode.magnitude = magnitude 47 | 48 | gen_coeff = pe.Node(niu.Function(function=_gen_coeff), name='gen_coeff') 49 | 50 | reg_wf = init_coeff2epi_wf(2, debug=True, sloppy=True, write_coeff=True) 51 | 52 | workflow = pe.Workflow(name='test_registration_wf') 53 | # fmt: off 54 | workflow.connect([ 55 | (epi_ref_wf, reg_wf, [ 56 | ("outputnode.fmap_ref", "inputnode.target_ref"), 57 | ("outputnode.fmap_mask", "inputnode.target_mask"), 58 | ]), 59 | (fmap_ref_wf, reg_wf, [ 60 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 61 | ("outputnode.fmap_mask", "inputnode.fmap_mask"), 62 | ]), 63 | (fmap_ref_wf, gen_coeff, [("outputnode.fmap_ref", "img")]), 64 | (gen_coeff, reg_wf, [("out", "inputnode.fmap_coeff")]), 65 | ]) 66 | # fmt: on 67 | 68 | if outdir: 69 | from ...outputs import DerivativesDataSink 70 | 71 | report = pe.Node( 72 | SimpleBeforeAfter( 73 | after_label='Target EPI', 74 | before_label='B0 Reference', 75 | ), 76 | name='report', 77 | mem_gb=0.1, 78 | ) 79 | ds_report = pe.Node( 80 | DerivativesDataSink( 81 | base_directory=str(outdir), 82 | suffix='fieldmap', 83 | space='sbref', 84 | datatype='figures', 85 | dismiss_entities=('fmap',), 86 | source_file=magnitude, 87 | ), 88 | name='ds_report', 89 | run_without_submitting=True, 90 | ) 91 | 92 | # fmt: off 93 | workflow.connect([ 94 | (fmap_ref_wf, report, [("outputnode.fmap_ref", "before")]), 95 | (reg_wf, report, [("outputnode.target_ref", "after")]), 96 | (report, ds_report, [("out_report", "in_file")]), 97 | ]) 98 | # fmt: on 99 | 100 | if workdir: 101 | workflow.base_dir = str(workdir) 102 | 103 | workflow.run(plugin='Linear') 104 | 105 | 106 | # def test_map_coeffs(tmpdir): 107 | # from pathlib import Path 108 | # tmpdir.chdir() 109 | # outnii = nb.load(_move_coeff( 110 | # str(Path(__file__).parent / "sample-coeff.nii.gz"), 111 | # [str(Path(__file__).parent / "sample-rigid_xfm.mat")] 112 | # )[0]) 113 | # vs = nb.affines.voxel_sizes(outnii.affine) 114 | # assert np.allclose(vs, (40., 40., 20.)) 115 | 116 | # dircos = outnii.affine[:3, :3] / vs 117 | # assert np.allclose(dircos, np.eye(3)) 118 | 119 | 120 | def _gen_coeff(img): 121 | from pathlib import Path 122 | 123 | from sdcflows.interfaces.bspline import bspline_grid 124 | 125 | out_file = Path('coeff.nii.gz').absolute() 126 | bspline_grid(img).to_filename(out_file) 127 | return str(out_file) 128 | 129 | 130 | # ## Just in case we want to test with epis: 131 | # reg_wf = init_coeff2epi_wf(2, debug=True, sloppy=True, write_coeff=True) 132 | # reg_wf.inputs.inputnode.fmap_coeff = [ 133 | # str(derivs_path / "sub-101006_coeff-1_desc-topup_fieldmap.nii.gz") 134 | # ] 135 | # reg_wf.inputs.inputnode.fmap_ref = str(derivs_path / "sub-101006_desc-pepolar_epiref.nii.gz") 136 | # reg_wf.inputs.inputnode.fmap_mask = str(derivs_path / "sub-101006_desc-pepolar_mask.nii.gz") 137 | # reg_wf.inputs.inputnode.target_ref = str( 138 | # derivs_path / "sub-101006_task-rest_dir-LR_desc-preproc_sbref.nii.gz" 139 | # ) 140 | # reg_wf.inputs.inputnode.target_mask = str( 141 | # derivs_path / "sub-101006_task-rest_dir-LR_desc-sbref_mask.nii.gz" 142 | # ) 143 | # fmt: off 144 | # workflow.connect([ 145 | # (reg_wf, unwarp_wf, [ 146 | # ("outputnode.target2fmap_xfm", "inputnode.data2fmap_xfm") 147 | # ]), 148 | # ]) 149 | # fmt:on 150 | -------------------------------------------------------------------------------- /sdcflows/workflows/fit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/workflows/fit/__init__.py -------------------------------------------------------------------------------- /sdcflows/workflows/fit/base.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Build a dataset-wide estimation workflow.""" 24 | 25 | 26 | def init_sdcflows_wf(): 27 | """Create a multi-subject, multi-estimator *SDCFlows* workflow.""" 28 | from nipype.pipeline.engine import Workflow 29 | from niworkflows.utils.bids import collect_participants 30 | 31 | from sdcflows import config 32 | from sdcflows.utils.wrangler import find_estimators 33 | from sdcflows.workflows.outputs import init_fmap_derivatives_wf, init_fmap_reports_wf 34 | 35 | # Create parent workflow 36 | workflow = Workflow(name='sdcflows_wf') 37 | workflow.base_dir = config.execution.work_dir 38 | 39 | subjects = collect_participants( 40 | config.execution.layout, 41 | config.execution.participant_label, 42 | ) 43 | estimators_record = {} 44 | for subject in subjects: 45 | estimators_record[subject] = find_estimators( 46 | layout=config.execution.layout, 47 | subject=subject, 48 | fmapless=config.workflow.fmapless, 49 | logger=config.loggers.cli, 50 | ) 51 | 52 | for subject, sub_estimators in estimators_record.items(): 53 | for estim in sub_estimators: 54 | estim_wf = estim.get_workflow( 55 | omp_nthreads=config.nipype.omp_nthreads, 56 | sloppy=False, 57 | debug=False, 58 | ) 59 | 60 | derivs_wf = init_fmap_derivatives_wf( 61 | output_dir=config.execution.output_dir, 62 | bids_fmap_id=estim.bids_id, 63 | write_coeff=True, 64 | name=f'fmap_derivatives_{estim.sanitized_id}', 65 | ) 66 | 67 | source_paths = [str(source.path.absolute()) for source in estim.sources] 68 | derivs_wf.inputs.inputnode.source_files = source_paths 69 | derivs_wf.inputs.inputnode.fmap_meta = [source.metadata for source in estim.sources] 70 | 71 | reportlets_wf = init_fmap_reports_wf( 72 | fmap_type=estim.method, 73 | output_dir=config.execution.output_dir, 74 | bids_fmap_id=estim.bids_id, 75 | name=f'fmap_reports_{estim.sanitized_id}', 76 | ) 77 | reportlets_wf.inputs.inputnode.source_files = source_paths 78 | 79 | # fmt:off 80 | workflow.connect([ 81 | (estim_wf, derivs_wf, [ 82 | ("outputnode.fmap", "inputnode.fieldmap"), 83 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 84 | ("outputnode.fmap_coeff", "inputnode.fmap_coeff"), 85 | ]), 86 | (estim_wf, reportlets_wf, [ 87 | ("outputnode.fmap", "inputnode.fieldmap"), 88 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 89 | ("outputnode.fmap_mask", "inputnode.fmap_mask"), 90 | ]), 91 | 92 | ]) 93 | # fmt:on 94 | 95 | return workflow 96 | -------------------------------------------------------------------------------- /sdcflows/workflows/fit/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/workflows/fit/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/workflows/fit/tests/test_fit.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test that workflows build.""" 24 | 25 | import sys 26 | 27 | import pytest 28 | 29 | from .. import ( 30 | fieldmap, # noqa 31 | pepolar, # noqa 32 | syn, # noqa 33 | ) 34 | 35 | 36 | @pytest.mark.parametrize( 37 | 'workflow,kwargs', 38 | ( 39 | ('sdcflows.workflows.fit.fieldmap.init_fmap_wf', {'mode': 'mapped'}), 40 | ('sdcflows.workflows.fit.fieldmap.init_fmap_wf', {}), 41 | ('sdcflows.workflows.fit.fieldmap.init_phdiff_wf', {'omp_nthreads': 1}), 42 | ('sdcflows.workflows.fit.pepolar.init_3dQwarp_wf', {}), 43 | ('sdcflows.workflows.fit.pepolar.init_topup_wf', {}), 44 | ('sdcflows.workflows.fit.syn.init_syn_sdc_wf', {'omp_nthreads': 1}), 45 | ), 46 | ) 47 | def test_build_1(workflow, kwargs): 48 | """Make sure the workflow builds.""" 49 | module = '.'.join(workflow.split('.')[:-1]) 50 | func = workflow.split('.')[-1] 51 | getattr(sys.modules[module], func)(**kwargs) 52 | -------------------------------------------------------------------------------- /sdcflows/workflows/fit/tests/test_pepolar.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test pepolar type of fieldmaps.""" 24 | 25 | import pytest 26 | from nipype.pipeline import engine as pe 27 | 28 | from ..pepolar import init_topup_wf 29 | 30 | 31 | @pytest.mark.slow 32 | @pytest.mark.parametrize('ds', ('ds001771', 'HCP101006')) 33 | def test_topup_wf(tmpdir, bids_layouts, workdir, outdir, ds): 34 | """Test preparation workflow.""" 35 | layout = bids_layouts[ds] 36 | epi_path = sorted( 37 | layout.get(suffix='epi', extension=['nii', 'nii.gz'], scope='raw'), 38 | key=lambda k: k.path, 39 | ) 40 | in_data = [f.path for f in epi_path] 41 | 42 | wf = pe.Workflow(name=f'topup_{ds}') 43 | topup_wf = init_topup_wf(omp_nthreads=2, debug=True, sloppy=True) 44 | metadata = [layout.get_metadata(f.path) for f in epi_path] 45 | 46 | topup_wf.inputs.inputnode.in_data = in_data 47 | topup_wf.inputs.inputnode.metadata = metadata 48 | 49 | if outdir: 50 | from ...outputs import init_fmap_derivatives_wf, init_fmap_reports_wf 51 | 52 | outdir = outdir / 'unittests' / f'topup_{ds}' 53 | fmap_derivatives_wf = init_fmap_derivatives_wf( 54 | output_dir=str(outdir), 55 | write_coeff=True, 56 | bids_fmap_id='pepolar_id', 57 | ) 58 | fmap_derivatives_wf.inputs.inputnode.source_files = in_data 59 | fmap_derivatives_wf.inputs.inputnode.fmap_meta = metadata 60 | 61 | fmap_reports_wf = init_fmap_reports_wf( 62 | output_dir=str(outdir), 63 | fmap_type='pepolar', 64 | ) 65 | fmap_reports_wf.inputs.inputnode.source_files = in_data 66 | 67 | # fmt: off 68 | wf.connect([ 69 | (topup_wf, fmap_reports_wf, [("outputnode.fmap", "inputnode.fieldmap"), 70 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 71 | ("outputnode.fmap_mask", "inputnode.fmap_mask")]), 72 | (topup_wf, fmap_derivatives_wf, [ 73 | ("outputnode.fmap", "inputnode.fieldmap"), 74 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 75 | ("outputnode.fmap_coeff", "inputnode.fmap_coeff"), 76 | ]), 77 | ]) 78 | # fmt: on 79 | else: 80 | wf.add_nodes([topup_wf]) 81 | 82 | if workdir: 83 | wf.base_dir = str(workdir) 84 | 85 | wf.run(plugin='Linear') 86 | -------------------------------------------------------------------------------- /sdcflows/workflows/fit/tests/test_phdiff.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test phase-difference type of fieldmaps.""" 24 | 25 | from json import loads 26 | from pathlib import Path 27 | 28 | import pytest 29 | 30 | from ..fieldmap import Workflow, init_fmap_wf 31 | 32 | 33 | @pytest.mark.slow 34 | @pytest.mark.parametrize( 35 | 'fmap_file', 36 | [ 37 | ('ds001600/sub-1/fmap/sub-1_acq-v4_phasediff.nii.gz',), 38 | ( 39 | 'ds001600/sub-1/fmap/sub-1_acq-v2_phase1.nii.gz', 40 | 'ds001600/sub-1/fmap/sub-1_acq-v2_phase2.nii.gz', 41 | ), 42 | ('ds001771/derivatives/openneuro/sub-36/fmap/sub-36_acq-topup1_fieldmap.nii.gz',), 43 | ('HCP101006/sub-101006/fmap/sub-101006_phasediff.nii.gz',), 44 | ], 45 | ) 46 | def test_phdiff(tmpdir, datadir, workdir, outdir, fmap_file): 47 | """Test creation of the workflow.""" 48 | tmpdir.chdir() 49 | 50 | fmap_path = [datadir / f for f in fmap_file] 51 | fieldmaps = [ 52 | (str(f.absolute()), loads(Path(str(f).replace('.nii.gz', '.json')).read_text())) 53 | for f in fmap_path 54 | ] 55 | 56 | wf = Workflow(name=f'phdiff_{fmap_path[0].name.replace(".nii.gz", "").replace("-", "_")}') 57 | mode = 'mapped' if 'fieldmap' in fmap_path[0].name else 'phasediff' 58 | phdiff_wf = init_fmap_wf( 59 | omp_nthreads=2, 60 | debug=True, 61 | sloppy=True, 62 | mode=mode, 63 | ) 64 | phdiff_wf.inputs.inputnode.fieldmap = fieldmaps 65 | phdiff_wf.inputs.inputnode.magnitude = [ 66 | f.replace('diff', '1').replace('phase', 'magnitude').replace('fieldmap', 'magnitude') 67 | for f, _ in fieldmaps 68 | ] 69 | 70 | if outdir: 71 | from ...outputs import init_fmap_derivatives_wf, init_fmap_reports_wf 72 | 73 | outdir = outdir / 'unittests' / fmap_file[0].split('/')[0] 74 | fmap_derivatives_wf = init_fmap_derivatives_wf( 75 | output_dir=str(outdir), 76 | write_coeff=True, 77 | bids_fmap_id='phasediff_id', 78 | ) 79 | fmap_derivatives_wf.inputs.inputnode.source_files = [f for f, _ in fieldmaps] 80 | fmap_derivatives_wf.inputs.inputnode.fmap_meta = [f for _, f in fieldmaps] 81 | 82 | fmap_reports_wf = init_fmap_reports_wf( 83 | output_dir=str(outdir), 84 | fmap_type=mode if len(fieldmaps) == 1 else 'phases', 85 | ) 86 | fmap_reports_wf.inputs.inputnode.source_files = [f for f, _ in fieldmaps] 87 | 88 | # fmt: off 89 | wf.connect([ 90 | (phdiff_wf, fmap_reports_wf, [ 91 | ("outputnode.fmap", "inputnode.fieldmap"), 92 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 93 | ("outputnode.fmap_mask", "inputnode.fmap_mask")]), 94 | (phdiff_wf, fmap_derivatives_wf, [ 95 | ("outputnode.fmap", "inputnode.fieldmap"), 96 | ("outputnode.fmap_ref", "inputnode.fmap_ref"), 97 | ("outputnode.fmap_coeff", "inputnode.fmap_coeff"), 98 | ]), 99 | ]) 100 | # fmt: on 101 | else: 102 | wf.add_nodes([phdiff_wf]) 103 | 104 | if workdir: 105 | wf.base_dir = str(workdir) 106 | 107 | wf.run(plugin='Linear') 108 | -------------------------------------------------------------------------------- /sdcflows/workflows/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipreps/sdcflows/b4a01f6e2071a538896e228cba9a43c10b8256cb/sdcflows/workflows/tests/__init__.py -------------------------------------------------------------------------------- /sdcflows/workflows/tests/test_ancillary.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Check the tools submodule.""" 24 | 25 | import pytest 26 | from nipype.interfaces import utility as niu 27 | from nipype.pipeline import engine as pe 28 | from nireports.interfaces.reporting.masks import SimpleShowMaskRPT 29 | 30 | from ..ancillary import init_brainextraction_wf 31 | 32 | 33 | @pytest.mark.slow 34 | @pytest.mark.parametrize('folder', ['magnitude/ds000054', 'magnitude/ds000217']) 35 | def test_brainmasker(tmpdir, datadir, workdir, outdir, folder): 36 | """Exercise the brain masking tool.""" 37 | tmpdir.chdir() 38 | 39 | wf = pe.Workflow(name=f'test_mask_{folder.replace("/", "_")}') 40 | if workdir: 41 | wf.base_dir = str(workdir) 42 | 43 | input_files = [str(f) for f in (datadir / 'brain-extraction-tests' / folder).glob('*.nii.gz')] 44 | 45 | inputnode = pe.Node(niu.IdentityInterface(fields=('in_file',)), name='inputnode') 46 | inputnode.iterables = ('in_file', input_files) 47 | merger = pe.Node(niu.Function(function=_merge), name='merger') 48 | 49 | brainmask_wf = init_brainextraction_wf() 50 | 51 | # fmt:off 52 | wf.connect([ 53 | (inputnode, merger, [("in_file", "in_file")]), 54 | (merger, brainmask_wf, [("out", "inputnode.in_file")]), 55 | ]) 56 | # fmt:on 57 | 58 | if outdir: 59 | out_path = outdir / 'masks' / folder.split('/')[-1] 60 | out_path.mkdir(exist_ok=True, parents=True) 61 | report = pe.Node(SimpleShowMaskRPT(), name='report') 62 | report.interface._always_run = True 63 | 64 | def _report_name(fname, out_path): 65 | from pathlib import Path 66 | 67 | return str( 68 | out_path 69 | / Path(fname) 70 | .name.replace('.nii', '_mask.svg') 71 | .replace('_magnitude', '_desc-magnitude') 72 | .replace('.gz', '') 73 | ) 74 | 75 | # fmt: off 76 | wf.connect([ 77 | (inputnode, report, [(("in_file", _report_name, out_path), "out_report")]), 78 | (brainmask_wf, report, [("outputnode.out_mask", "mask_file"), 79 | ("outputnode.out_file", "background_file")]), 80 | ]) 81 | # fmt: on 82 | 83 | wf.run() 84 | 85 | 86 | def _merge(in_file): 87 | import nibabel as nb 88 | import numpy as np 89 | 90 | img = nb.squeeze_image(nb.load(in_file)) 91 | 92 | data = np.asanyarray(img.dataobj) 93 | if data.ndim == 3: 94 | return in_file 95 | 96 | from pathlib import Path 97 | 98 | data = data.mean(-1) 99 | out_file = (Path() / 'merged.nii.gz').absolute() 100 | img.__class__(data, img.affine, img.header).to_filename(out_file) 101 | return str(out_file) 102 | -------------------------------------------------------------------------------- /sdcflows/workflows/tests/test_base.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | # 4 | # Copyright 2021 The NiPreps Developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # We support and encourage derived works from this project, please read 19 | # about our expectations at 20 | # 21 | # https://www.nipreps.org/community/licensing/ 22 | # 23 | """Test the base workflow.""" 24 | 25 | from pathlib import Path 26 | 27 | import pytest 28 | 29 | from sdcflows import fieldmaps as fm 30 | from sdcflows.utils.wrangler import find_estimators 31 | from sdcflows.workflows.base import init_fmap_preproc_wf 32 | 33 | 34 | @pytest.mark.veryslow 35 | @pytest.mark.slow 36 | @pytest.mark.parametrize('dataset,subject', [('ds000054', '100185'), ('HCP101006', '101006')]) 37 | def test_fmap_wf(tmpdir, workdir, outdir, bids_layouts, dataset, subject): 38 | """Test the encompassing of the wrangler and the workflow creator.""" 39 | if outdir is None: 40 | outdir = Path(str(tmpdir)) 41 | 42 | outdir = outdir / 'test_base' / dataset 43 | fm._estimators.clear() 44 | estimators = find_estimators(layout=bids_layouts[dataset], subject=subject) 45 | wf = init_fmap_preproc_wf( 46 | estimators=estimators, 47 | omp_nthreads=2, 48 | output_dir=str(outdir), 49 | subject=subject, 50 | debug=True, 51 | ) 52 | 53 | # PEPOLAR and fieldmap-less solutions typically cannot work directly on the 54 | # raw inputs. Normally, some ad-hoc massaging and pre-processing is required. 55 | # For that reason, the inputs cannot be set implicitly by init_fmap_preproc_wf. 56 | for estimator in estimators: 57 | if estimator.method != fm.EstimatorType.PEPOLAR: 58 | continue 59 | 60 | inputnode = wf.get_node(f'in_{estimator.bids_id}') 61 | inputnode.inputs.in_data = [str(f.path) for f in estimator.sources] 62 | inputnode.inputs.metadata = [f.metadata for f in estimator.sources] 63 | 64 | if workdir: 65 | wf.base_dir = str(workdir) 66 | 67 | res = wf.run(plugin='Linear') 68 | 69 | # Regression test for when out_merge_fmap_coeff was flattened and would 70 | # have twice as many elements as the other nodes 71 | assert all( 72 | len(node.result.outputs.out) == len(estimators) 73 | for node in res.nodes 74 | if node.name.startswith('out_merge_') 75 | ) 76 | -------------------------------------------------------------------------------- /tools/cache_templateflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # /// script 3 | # requires-python = ">=3.9" 4 | # dependencies = [ 5 | # "templateflow", 6 | # ] 7 | # /// 8 | from templateflow import api as tfapi 9 | 10 | tfapi.get('MNI152NLin2009cAsym', resolution=2, desc='brain', suffix='mask') 11 | tfapi.get('MNI152NLin2009cAsym', resolution=1, label='brain', suffix='probseg') 12 | tfapi.get('MNI152NLin2009cAsym', resolution=2, desc='fMRIPrep', suffix='boldref') 13 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | requires = 3 | tox>=4 4 | tox-uv 5 | envlist = 6 | py3{9,10,11,12,13}-latest-{fast,slow,veryslow} 7 | py39-min-fast 8 | py3{11,12,13}-pre-{fast,slow,veryslow} 9 | style 10 | spellcheck 11 | skip_missing_interpreters = true 12 | 13 | # Configuration that allows us to split tests across GitHub runners effectively 14 | [gh-actions] 15 | python = 16 | 3.9: py39 17 | 3.10: py310 18 | 3.11: py311 19 | 3.12: py312 20 | 3.13: py313 21 | 22 | [gh-actions:env] 23 | DEPENDS = 24 | min: min 25 | latest: latest 26 | pre: pre 27 | 28 | MARKS = 29 | fast: fast 30 | slow: slow 31 | veryslow: veryslow 32 | 33 | [testenv] 34 | description = Pytest with coverage 35 | labels = test 36 | pip_pre = 37 | pre: true 38 | pass_env = 39 | TEMPLATEFLOW_HOME 40 | # Freesurfer variables searched for 41 | FREESURFER_HOME 42 | SUBJECTS_DIR 43 | FS_LICENSE 44 | # FSL 45 | FSLOUTPUTTYPE 46 | FSLMULTIFILEQUIT 47 | # AFNI 48 | AFNI_HOME 49 | AFNI_MODELPATH 50 | AFNI_IMSAVE_WARNINGS 51 | AFNI_TTATLAS_DATASET 52 | AFNI_PLUGINPATH 53 | # CI variables 54 | TEST_DATA_HOME 55 | TEST_OUTPUT_DIR 56 | TEST_WORK_DIR 57 | FMRIPREP_REGRESSION_SOURCE 58 | CACHED_WORK_DIRECTORY 59 | # CircleCI-specific 60 | CIRCLE_NPROCS 61 | SAVE_CIRCLE_ARTIFACTS 62 | # getpass.getuser() sources for Windows: 63 | LOGNAME 64 | USER 65 | LNAME 66 | USERNAME 67 | # Pass user color preferences through 68 | PY_COLORS 69 | FORCE_COLOR 70 | NO_COLOR 71 | CLICOLOR 72 | CLICOLOR_FORCE 73 | PYTHON_GIL 74 | deps = 75 | # Waiting on a release 76 | py313: traits @ git+https://github.com/enthought/traits.git@10954eb 77 | extras = tests 78 | setenv = 79 | pre: PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple 80 | pre: UV_INDEX=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple 81 | pre: UV_INDEX_STRATEGY=unsafe-best-match 82 | uv_resolution = 83 | min: lowest-direct 84 | 85 | commands = 86 | pytest --durations=20 --durations-min=1.0 --cov-report term-missing \ 87 | fast: -m "not slow" \ 88 | slow: -m "slow and not veryslow" \ 89 | veryslow: -m "veryslow" \ 90 | {posargs:-n auto} 91 | 92 | [testenv:style] 93 | description = Check our style guide 94 | labels = check 95 | deps = 96 | ruff 97 | skip_install = true 98 | commands = 99 | ruff check --diff 100 | ruff format --diff 101 | 102 | [testenv:style-fix] 103 | description = Auto-apply style guide to the extent possible 104 | labels = pre-release 105 | deps = 106 | ruff 107 | skip_install = true 108 | commands = 109 | ruff check --fix 110 | ruff format 111 | ruff check --select ISC001 112 | 113 | [testenv:spellcheck] 114 | description = Check spelling 115 | labels = check 116 | deps = 117 | codespell[toml] 118 | skip_install = true 119 | commands = 120 | codespell . {posargs} 121 | 122 | [testenv:build{,-strict}] 123 | labels = 124 | check 125 | pre-release 126 | deps = 127 | build 128 | twine 129 | skip_install = true 130 | set_env = 131 | # Ignore specific known warnings: 132 | # https://github.com/pypa/pip/issues/11684 133 | # https://github.com/pypa/pip/issues/12243 134 | strict: PYTHONWARNINGS=error,once:pkg_resources is deprecated as an API.:DeprecationWarning:pip._internal.metadata.importlib._envs,once:Unimplemented abstract methods {'locate_file'}:DeprecationWarning:pip._internal.metadata.importlib._dists 135 | commands = 136 | python -m build 137 | python -m twine check dist/* 138 | 139 | [testenv:publish] 140 | depends = build 141 | labels = release 142 | deps = 143 | twine 144 | skip_install = true 145 | commands = 146 | python -m twine upload dist/* 147 | --------------------------------------------------------------------------------