diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9a0ce5..7d9a373 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,17 +2,11 @@ name: Test on: [push, pull_request] jobs: check: - if: github.event_name != 'push' || github.ref != 'refs/heads/devel' + if: github.event_name != 'pull_request' || github.repository_owner != 'AMYPAD' runs-on: ubuntu-latest - strategy: - matrix: - python: [2.7, 3.6, 3.9] - name: Check py${{ matrix.python }} steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - name: set PYSHA run: echo "PYSHA=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - uses: actions/cache@v1 @@ -22,7 +16,7 @@ jobs: - name: dependencies run: pip install -U pre-commit - uses: reviewdog/action-setup@v1 - - if: github.event_name != 'schedule' + - if: github.event_name == 'push' || github.event_name == 'pull_request' name: comment run: | if [[ $EVENT == pull_request ]]; then @@ -35,15 +29,14 @@ jobs: env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} EVENT: ${{ github.event_name }} - - if: startsWith(matrix.python, '3') - run: pre-commit run -a --show-diff-on-failure + - run: SKIP=mbeautify pre-commit run -a --show-diff-on-failure test: - if: github.event_name != 'pull_request' || github.head_ref != 'devel' + if: github.event_name != 'pull_request' || github.repository_owner != 'AMYPAD' + name: py${{ matrix.python }} runs-on: ubuntu-latest strategy: matrix: python: [2.7, 3.6, 3.9] - name: Test py${{ matrix.python }} steps: - uses: actions/checkout@v2 with: @@ -56,22 +49,25 @@ jobs: run: pytest --durations-min=1 - if: startsWith(matrix.python, '2') run: pytest - - run: codecov + - uses: codecov/codecov-action@v1 matlab: - if: github.event_name != 'pull_request' || github.head_ref != 'devel' + if: github.event_name != 'pull_request' || github.repository_owner != 'AMYPAD' + name: matlab py${{ matrix.python }} runs-on: [self-hosted, python, matlab] - name: Test matlab + strategy: + matrix: + python: [3.9] steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Run setup-python - run: setup-python -p3.7 + run: setup-python -p${{ matrix.python }} - run: pip install -U .[dev] - run: pytest --durations-min=1 - - run: codecov + - uses: codecov/codecov-action@v1 - name: Post Run setup-python - run: setup-python -p3.7 -Dr + run: setup-python -p${{ matrix.python }} -Dr if: ${{ always() }} deploy: needs: [check, test, matlab] @@ -85,7 +81,8 @@ jobs: - id: dist uses: casperdcl/deploy-pypi@v2 with: - pip: true + requirements: twine setuptools wheel setuptools_scm[toml] + build: true gpg_key: ${{ secrets.GPG_KEY }} password: ${{ secrets.PYPI_TOKEN }} upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8d77ba8..dc1a8dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -21,25 +21,33 @@ repos: - id: todo name: Check TODO language: pygrep + args: [-i] entry: TODO types: [text] exclude: ^(.pre-commit-config.yaml|.github/workflows/test.yml)$ - args: [-i] - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 args: [-j8] additional_dependencies: + - flake8-broken-line - flake8-bugbear - flake8-comprehensions - flake8-debugger + - flake8-isort - flake8-string-format -- repo: https://github.com/psf/black - rev: 21.7b0 +- repo: https://github.com/google/yapf + rev: v0.32.0 hooks: - - id: black + - id: yapf + args: [-i] + additional_dependencies: [toml] - repo: https://github.com/PyCQA/isort - rev: 5.9.3 + rev: 5.10.1 hooks: - id: isort +- hooks: + - id: mbeautify + repo: https://github.com/AMYPAD/miutil + rev: v0.9.1 diff --git a/LICENCE.md b/LICENCE.md index ee3b9f9..8019c8c 100644 --- a/LICENCE.md +++ b/LICENCE.md @@ -1,4 +1,4 @@ -Copyright 2020 AMYPAD +Copyright 2020-2022 AMYPAD Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. diff --git a/pyproject.toml b/pyproject.toml index 9142a26..7a9e46f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,6 @@ [build-system] requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] -build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "spm12/_dist_ver.py" write_to_template = "__version__ = '{version}'\n" - -[tool.black] -target_version = ["py27", "py36", "py38"] diff --git a/setup.cfg b/setup.cfg index 3a4310a..bc4d994 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,8 +7,8 @@ license=Apache 2.0 license_file=LICENCE.md url=https://github.com/AMYPAD/SPM12 project_urls= - Changelog = https://github.com/AMYPAD/SPM12/releases - Documentation = https://github.com/AMYPAD/SPM12/#SPM12 + Changelog=https://github.com/AMYPAD/SPM12/releases + Documentation=https://github.com/AMYPAD/SPM12/#SPM12 Upstream Project=https://www.fil.ion.ucl.ac.uk/spm maintainer=Casper da Costa-Luis maintainer_email=casper.dcl@physics.org @@ -36,32 +36,28 @@ classifiers= Topic :: Software Development :: Libraries Topic :: System :: Installation/Setup [options] -zip_safe = False +zip_safe=False setup_requires=setuptools>=42; wheel; setuptools_scm[toml]>=3.4 install_requires= argopt - miutil[nii,web]>=0.7.2 - setuptools # pkg_resources + miutil[nii,web]>=0.8.0 numpy scipy + setuptools include_package_data=True packages=find: python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* [options.extras_require] dev= - pre-commit - twine - wheel pytest pytest-cov pytest-timeout pytest-xdist - codecov demo= miutil[plot]>=0.3.0 matplotlib [options.entry_points] -console_scripts = +console_scripts= spm12=spm12.cli:main [options.packages.find] exclude=tests @@ -69,15 +65,27 @@ exclude=tests *=*.md, *.rst, *.m [bdist_wheel] -universal = 1 +universal=1 [flake8] -max_line_length=88 -extend-ignore=E203,P1 +max_line_length=99 +extend-ignore=P1,E261 exclude=.git,__pycache__,build,dist,.eggs +[yapf] +spaces_before_comment=15, 20 +arithmetic_precedence_indication=true +allow_split_before_dict_value=false +coalesce_brackets=True +column_limit=99 +each_dict_entry_on_separate_line=False +space_between_ending_comma_and_closing_bracket=False +split_before_named_assigns=False +split_before_closing_bracket=False + [isort] profile=black +line_length=99 known_first_party=spm12,tests [tool:pytest] diff --git a/spm12/__init__.py b/spm12/__init__.py index 2755ed5..5a285ad 100644 --- a/spm12/__init__.py +++ b/spm12/__init__.py @@ -1,5 +1,5 @@ -from .regseg import * # NOQA -from .utils import * # NOQA +from .regseg import * # yapf: disable # NOQA +from .utils import * # yapf: disable # NOQA # version detector. Precedence: installed dist, git, 'UNKNOWN' try: diff --git a/spm12/__main__.py b/spm12/__main__.py index daf9fc5..178f37e 100755 --- a/spm12/__main__.py +++ b/spm12/__main__.py @@ -3,5 +3,5 @@ from .cli import main -if __name__ == "__main__": # pragma: no cover +if __name__ == "__main__": # pragma: no cover sys.exit(main(sys.argv[1:])) diff --git a/spm12/amypad_coreg.m b/spm12/amypad_coreg.m index ae8199d..9eacfbf 100644 --- a/spm12/amypad_coreg.m +++ b/spm12/amypad_coreg.m @@ -1,28 +1,28 @@ function [M, x] = amypad_coreg(imref, imflo, costfun, sep, tol, fwhm, params, graphics, visual) - if visual>0 - Fgraph = spm_figure('GetWin','Graphics'); - Finter = spm_figure('GetWin','Interactive'); - end +if visual > 0 + Fgraph = spm_figure('GetWin', 'Graphics'); + Finter = spm_figure('GetWin', 'Interactive'); +end - cflags.cost_fun = costfun; - cflags.sep = sep; - cflags.tol = tol; - cflags.fwhm = fwhm; - cflags.params = params; - cflags.graphics = graphics; +cflags.cost_fun = costfun; +cflags.sep = sep; +cflags.tol = tol; +cflags.fwhm = fwhm; +cflags.params = params; +cflags.graphics = graphics; - VG = strcat(imref,',1'); - VF = strcat(imflo,',1'); +VG = strcat(imref, ',1'); +VF = strcat(imflo, ',1'); - %disp('Matlab internal reference image:'); disp(imref); - %disp('Matlab internal floating image:'); disp(imflo); - %disp(cflags); - %disp(tol); +%disp('Matlab internal reference image:'); disp(imref); +%disp('Matlab internal floating image:'); disp(imflo); +%disp(cflags); +%disp(tol); - spm_jobman('initcfg') - x = spm_coreg(VG, VF, cflags); - M = spm_matrix(x); +spm_jobman('initcfg') +x = spm_coreg(VG, VF, cflags); +M = spm_matrix(x); - %disp('translations and rotations:'); disp(x); - %disp('affine matrix:'); disp(M); +%disp('translations and rotations:'); disp(x); +%disp('affine matrix:'); disp(M); end diff --git a/spm12/amypad_coreg_modify_affine.m b/spm12/amypad_coreg_modify_affine.m index 569f74c..be1e28f 100644 --- a/spm12/amypad_coreg_modify_affine.m +++ b/spm12/amypad_coreg_modify_affine.m @@ -1,7 +1,7 @@ function out = amypad_coreg_modify_affine(imflo, M) - VF = strcat(imflo,',1'); - MM = zeros(4,4); - MM(:,:) = spm_get_space(VF); - spm_get_space(VF, M\MM(:,:)); - out = 0; +VF = strcat(imflo, ',1'); +MM = zeros(4, 4); +MM(:, :) = spm_get_space(VF); +spm_get_space(VF, M \ MM(:, :)); +out = 0; end diff --git a/spm12/amypad_normw.m b/spm12/amypad_normw.m index f7570a5..d80755d 100644 --- a/spm12/amypad_normw.m +++ b/spm12/amypad_normw.m @@ -1,10 +1,10 @@ function out = amypad_normw(def_file, flist4norm) - job.subj.def = {def_file}; - job.subj.resample = flist4norm; - job.woptions.bb = [NaN, NaN, NaN; NaN, NaN, NaN]; - job.woptions.vox = [2, 2, 2]; - job.woptions.interp = 4; - job.woptions.prefix = 'w'; - spm_run_norm(job); - out=0; +job.subj.def = {def_file}; +job.subj.resample = flist4norm; +job.woptions.bb = [NaN, NaN, NaN; NaN, NaN, NaN]; +job.woptions.vox = [2, 2, 2]; +job.woptions.interp = 4; +job.woptions.prefix = 'w'; +spm_run_norm(job); +out = 0; end diff --git a/spm12/amypad_resample.m b/spm12/amypad_resample.m index c942f85..921110f 100644 --- a/spm12/amypad_resample.m +++ b/spm12/amypad_resample.m @@ -1,23 +1,23 @@ function out = amypad_resample(imref, imflo, M, f_mask, f_mean, f_interp, f_which, f_prefix) - %-Reslicing parameters - rflags.mask = f_mask; - rflags.mean = f_mean; - rflags.interp = f_interp; - rflags.which = f_which; - rflags.wrap = [0 0 0]; - rflags.prefix = f_prefix; +%-Reslicing parameters +rflags.mask = f_mask; +rflags.mean = f_mean; +rflags.interp = f_interp; +rflags.which = f_which; +rflags.wrap = [0, 0, 0]; +rflags.prefix = f_prefix; - VG = strcat(imref,',1'); - VF = strcat(imflo,',1'); +VG = strcat(imref, ',1'); +VF = strcat(imflo, ',1'); - % disp('Matlab internal reference image:'); disp(imref); - % disp('Matlab internal floating image:'); disp(imflo); - % disp(rflags) +% disp('Matlab internal reference image:'); disp(imref); +% disp('Matlab internal floating image:'); disp(imflo); +% disp(rflags) - MM = zeros(4,4); - MM(:, :) = spm_get_space(VF); - spm_get_space(VF, M\MM(:, :)); - P = {VG; VF}; - spm_reslice(P, rflags); - out = 0; +MM = zeros(4, 4); +MM(:, :) = spm_get_space(VF); +spm_get_space(VF, M \ MM(:, :)); +P = {VG; VF}; +spm_reslice(P, rflags); +out = 0; end diff --git a/spm12/amypad_seg.m b/spm12/amypad_seg.m index 42bf585..9f4f4df 100644 --- a/spm12/amypad_seg.m +++ b/spm12/amypad_seg.m @@ -1,46 +1,46 @@ -function [param,invdef,fordef] = amypad_seg(f_mri, spm_path, nat_gm, nat_wm, nat_csf, store_fwd, store_inv, visual) - job.channel.vols = {strcat(f_mri,',1')}; - job.channel.biasreg = 0.001; - job.channel.biasfwhm = 60; - job.channel.write = [0, 0]; - job.tissue(1).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,1']}; - job.tissue(1).ngaus = 1; - job.tissue(1).native = [nat_gm, 0]; - job.tissue(1).warped = [0, 0]; - job.tissue(2).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,2']}; - job.tissue(2).ngaus = 1; - job.tissue(2).native = [nat_wm, 0]; - job.tissue(2).warped = [0, 0]; - job.tissue(3).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,3']}; - job.tissue(3).ngaus = 2; - job.tissue(3).native = [nat_csf, 0]; - job.tissue(3).warped = [0, 0]; - job.tissue(4).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,4']}; - job.tissue(4).ngaus = 3; - job.tissue(4).native = [0, 0]; - job.tissue(4).warped = [0, 0]; - job.tissue(5).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,5']}; - job.tissue(5).ngaus = 4; - job.tissue(5).native = [0, 0]; - job.tissue(5).warped = [0, 0]; - job.tissue(6).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,6']}; - job.tissue(6).ngaus = 2; - job.tissue(6).native = [0, 0]; - job.tissue(6).warped = [0, 0]; - job.warp.mrf = 1; - job.warp.cleanup = 1; - job.warp.reg = [0, 0.001, 0.5, 0.05, 0.2]; - job.warp.affreg = 'mni'; - job.warp.fwhm = 0; - job.warp.samp = 3; - job.warp.write = [store_fwd, store_inv]; - if visual>0 - Finter = spm_figure('GetWin','Interactive'); - end - spm_jobman('initcfg'); - segout = spm_preproc_run(job); - param = segout.param{1}; - invdef = segout.invdef{1}; - fordef = segout.fordef{1}; - %disp(segout); +function [param, invdef, fordef] = amypad_seg(f_mri, spm_path, nat_gm, nat_wm, nat_csf, store_fwd, store_inv, visual) +job.channel.vols = {strcat(f_mri, ',1')}; +job.channel.biasreg = 0.001; +job.channel.biasfwhm = 60; +job.channel.write = [0, 0]; +job.tissue(1).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,1']}; +job.tissue(1).ngaus = 1; +job.tissue(1).native = [nat_gm, 0]; +job.tissue(1).warped = [0, 0]; +job.tissue(2).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,2']}; +job.tissue(2).ngaus = 1; +job.tissue(2).native = [nat_wm, 0]; +job.tissue(2).warped = [0, 0]; +job.tissue(3).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,3']}; +job.tissue(3).ngaus = 2; +job.tissue(3).native = [nat_csf, 0]; +job.tissue(3).warped = [0, 0]; +job.tissue(4).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,4']}; +job.tissue(4).ngaus = 3; +job.tissue(4).native = [0, 0]; +job.tissue(4).warped = [0, 0]; +job.tissue(5).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,5']}; +job.tissue(5).ngaus = 4; +job.tissue(5).native = [0, 0]; +job.tissue(5).warped = [0, 0]; +job.tissue(6).tpm = {[spm_path, filesep, 'tpm', filesep, 'TPM.nii,6']}; +job.tissue(6).ngaus = 2; +job.tissue(6).native = [0, 0]; +job.tissue(6).warped = [0, 0]; +job.warp.mrf = 1; +job.warp.cleanup = 1; +job.warp.reg = [0, 0.001, 0.5, 0.05, 0.2]; +job.warp.affreg = 'mni'; +job.warp.fwhm = 0; +job.warp.samp = 3; +job.warp.write = [store_fwd, store_inv]; +if visual > 0 + Finter = spm_figure('GetWin', 'Interactive'); +end +spm_jobman('initcfg'); +segout = spm_preproc_run(job); +param = segout.param{1}; +invdef = segout.invdef{1}; +fordef = segout.fordef{1}; +%disp(segout); end diff --git a/spm12/cli.py b/spm12/cli.py index 8f005b3..c7bcc77 100644 --- a/spm12/cli.py +++ b/spm12/cli.py @@ -1,22 +1,34 @@ """Usage: - spm12 [options] + spm12 [options] [...] Options: -c DIR, --cache DIR : directory to use for installation [default: ~/.spm12]. -s VER, --spm-version : version [default: 12]. + -r, --runtime : use runtime (not full MATLAB). + +Arguments: + : Runtime command [default: quit]|gui|help|... """ import logging from argopt import argopt -from .utils import ensure_spm +from .utils import ensure_spm, mcr_run + +log = logging.getLogger(__name__) def main(argv=None): - logging.basicConfig( - level=logging.DEBUG, format="%(levelname)s:%(funcName)s:%(message)s" - ) + logging.basicConfig(level=logging.DEBUG, format="%(levelname)s:%(funcName)s:%(message)s") args = argopt(__doc__).parse_args(argv) - ensure_spm(cache=args.cache, version=args.spm_version) + log.info(args) + if isinstance(args.command, str): + args.command = [args.command] + if len(args.command) == 1 and args.command[0] == "gui": + args.command = [] + if args.runtime: + log.debug(mcr_run(*args.command, cache=args.cache, version=args.spm_version)) + else: + ensure_spm(cache=args.cache, version=args.spm_version) print("SPM{v} is successfully installed".format(v=args.spm_version)) return 0 diff --git a/spm12/regseg.py b/spm12/regseg.py index 5d5983b..3f9453e 100644 --- a/spm12/regseg.py +++ b/spm12/regseg.py @@ -33,7 +33,7 @@ def glob_match(pttrn, pth): def fwhm2sig(fwhm, voxsize=2.0): - return fwhm / (voxsize * (8 * np.log(2)) ** 0.5) + return fwhm / (voxsize * (8 * np.log(2))**0.5) def smoothim(fim, fwhm=4, fout=""): @@ -41,14 +41,11 @@ def smoothim(fim, fwhm=4, fout=""): Smooth image using Gaussian filter with FWHM given as an option. """ imd = nii.getnii(fim, output="all") - imsmo = ndi.filters.gaussian_filter( - imd["im"], fwhm2sig(fwhm, voxsize=imd["voxsize"]), mode="constant" - ) + imsmo = ndi.filters.gaussian_filter(imd["im"], fwhm2sig(fwhm, voxsize=imd["voxsize"]), + mode="constant") if not fout: f = nii.file_parts(fim) - fout = os.path.join( - f[0], "{}_smo{}{}".format(f[1], str(fwhm).replace(".", "-"), f[2]) - ) + fout = os.path.join(f[0], "{}_smo{}{}".format(f[1], str(fwhm).replace(".", "-"), f[2])) nii.array2nii( imsmo, imd["affine"], @@ -95,24 +92,15 @@ def coreg_spm( image according to the rigid body transformation. """ out = {} # output dictionary + sep = sep or [4, 2] tol = tol or [ - 0.0200, - 0.0200, - 0.0200, - 0.0010, - 0.0010, - 0.0010, - 0.0100, - 0.0100, - 0.0100, - 0.0010, - 0.0010, - 0.0010, - ] + 0.0200, 0.0200, 0.0200, 0.0010, 0.0010, 0.0010, 0.0100, 0.0100, 0.0100, 0.0010, 0.0010, + 0.0010] fwhm = fwhm or [7, 7] params = params or [0, 0, 0, 0, 0, 0] - eng = ensure_spm(matlab_eng_name) # get_matlab + + eng = ensure_spm(matlab_eng_name) # get_matlab if not outpath and fname_aff and "/" in fname_aff: opth = os.path.dirname(fname_aff) or os.path.dirname(imflo) @@ -135,12 +123,7 @@ def coreg_spm( # delete the previous version (non-smoothed) os.remove(imrefu) imrefu = smodct["fim"] - - log.info( - "smoothed the reference image with FWHM={} and saved to\n{}".format( - fwhm_ref, imrefu - ) - ) + log.info("smoothed the reference image with FWHM=%r and saved to\n%r", fwhm_ref, imrefu) # floating if hasext(imflo, "gz"): @@ -158,14 +141,8 @@ def coreg_spm( else: # save the uncompressed and unsmoothed version imflou_ = imflou - imflou = smodct["fim"] - - log.info( - "smoothed the floating image with FWHM={} and saved to\n{}".format( - fwhm_ref, imrefu - ) - ) + log.info("smoothed the floating image with FWHM=%r and saved to\n%r", fwhm_ref, imrefu) # run the MATLAB SPM registration import matlab as ml @@ -257,16 +234,13 @@ def resample_spm( del_out_uncmpr=False, ): log.debug( - dedent( - """\ + dedent("""\ ====================================================================== - S P M inputs: - > ref:' {} - > flo:' {} - ======================================================================""" - ).format(imref, imflo) - ) - eng = ensure_spm(matlab_eng_name) # get_matlab + SPM inputs: + > ref: %r + > flo: %r + ======================================================================"""), imref, imflo) + eng = ensure_spm(matlab_eng_name) # get_matlab if not outpath and fimout: opth = os.path.dirname(fimout) or os.path.dirname(imflo) @@ -299,9 +273,7 @@ def resample_spm( M = np.load(M) log.info("matrix M given in the form of NumPy file") else: - raise IOError( - errno.ENOENT, M, "Unrecognised file extension for the affine." - ) + raise IOError(errno.ENOENT, M, "Unrecognised file extension for the affine.") elif isinstance(M, (np.ndarray, np.generic)): log.info("matrix M given in the form of Numpy array") else: @@ -310,9 +282,7 @@ def resample_spm( # run the Matlab SPM resampling import matlab as ml - eng.amypad_resample( - imrefu, imflou, ml.double(M.tolist()), mask, mean, intrp, which, prefix - ) + eng.amypad_resample(imrefu, imflou, ml.double(M.tolist()), mask, mean, intrp, which, prefix) # -compress the output split = os.path.split(imflou) @@ -345,11 +315,7 @@ def resample_spm( if fwhm > 0: smodct = smoothim(fout, fwhm) - log.info( - "smoothed the resampled image with FWHM={} and saved to\n{}".format( - fwhm, smodct["fim"] - ) - ) + log.info("smoothed the resampled image with FWHM=%r and saved to\n%r", fwhm, smodct["fim"]) return fout @@ -379,11 +345,13 @@ def seg_spm( visual: shows the Matlab window progress """ out = {} # output dictionary + # get Matlab engine or use the provided one eng = ensure_spm(matlab_eng_name) if not spm_path: spm_path = spm_dir() - # run SPM normalisation/segmentation + + # run SPM normalisation/segmentation param, invdef, fordef = eng.amypad_seg( f_mri, str(spm_path), @@ -400,6 +368,7 @@ def seg_spm( out["param"] = move_files(param, outpath) out["invdef"] = move_files(invdef, outpath) out["fordef"] = move_files(fordef, outpath) + # go through tissue types and move them to the output folder for c in glob_match(r"c\d*", os.path.dirname(param)): nm = os.path.basename(c)[:2] @@ -424,19 +393,17 @@ def normw_spm(f_def, files4norm, matlab_eng_name="", outpath=None): matlab_eng_name: name of the Python engine for Matlab. outpath: output folder path for the normalisation files """ - eng = ensure_spm(matlab_eng_name) # get_matlab + eng = ensure_spm(matlab_eng_name) # get_matlab eng.amypad_normw(f_def, files4norm) - out = [] # output list + out = [] # output list + if outpath is not None: create_dir(outpath) for f in files4norm: - fpth = f.split(",")[0] + fpth = f.rsplit(",", 1)[0] out.append( - move_files( - os.path.join(os.path.dirname(fpth), "w" + os.path.basename(fpth)), - outpath, - ) - ) + move_files(os.path.join(os.path.dirname(fpth), "w" + os.path.basename(fpth)), + outpath)) else: out.append("w" + os.path.basename(f.split(",")[0])) return out diff --git a/spm12/utils.py b/spm12/utils.py index afbadbb..1566e41 100644 --- a/spm12/utils.py +++ b/spm12/utils.py @@ -1,21 +1,58 @@ import logging +import os from functools import wraps from os import path +from subprocess import CalledProcessError, check_output from textwrap import dedent -from miutil.fdio import extractall -from miutil.mlab import get_engine +from miutil.fdio import Path, extractall, fspath +from miutil.mlab import get_engine, get_runtime from miutil.web import urlopen_cached from pkg_resources import resource_filename try: from functools import lru_cache -except ImportError: # fix py2.7 +except ImportError: # fix py2.7 from backports.functools_lru_cache import lru_cache __all__ = ["ensure_spm", "get_matlab", "spm_dir"] PATH_M = resource_filename(__name__, "") log = logging.getLogger(__name__) +SPM12_ZIP = "https://www.fil.ion.ucl.ac.uk/spm/download/restricted/eldorado/spm12.zip" +MCR_ZIP = "https://www.fil.ion.ucl.ac.uk/spm/download/restricted/utopia/spm12_r7771.zip" + + +def env_prefix(key, dir): + os.environ[key] = "%s%s%s" % (os.environ[key], os.pathsep, fspath(dir)) + + +def spm_runtime(cache="~/.spm12", version=12): + cache = Path(cache).expanduser() + if str(version) != "12": + raise NotImplementedError + runtime = cache / "runtime" + if not runtime.is_dir(): + log.info("Downloading to %s", cache) + with urlopen_cached(MCR_ZIP, cache) as fd: + extractall(fd, runtime) + + runner = runtime / "spm12" / "run_spm12.sh" + runner.chmod(0o755) + return fspath(runner) + + +def mcr_run(*cmd, cache="~/.spm12", version=12, mcr_version=713): + mcr_root = fspath(get_runtime(version=mcr_version)) + runner = spm_runtime(cache=cache, version=version) + try: + return check_output((runner, mcr_root) + cmd).decode("U8").strip() + except CalledProcessError as err: + raise RuntimeError( + dedent("""\ + {} + + See https://en.wikibooks.org/wiki/SPM/Standalone#Trouble-shooting + """).format(err)) @lru_cache() @@ -45,25 +82,18 @@ def ensure_spm(name=None, cache="~/.spm12", version=12): log.warning("MATLAB could not find SPM.") try: log.info("Downloading to %s", cache) - with urlopen_cached( - "https://www.fil.ion.ucl.ac.uk/" - "spm/download/restricted/eldorado/spm12.zip", - cache, - ) as fd: + with urlopen_cached(SPM12_ZIP, cache) as fd: extractall(fd, cache) eng.addpath(addpath) if not eng.exist("spm_jobman"): raise RuntimeError("MATLAB could not find SPM.") log.info("Installed") - except: # NOQA: E722,B001 + except: # NOQA: E722,B001 raise ImportError( - dedent( - """\ + dedent("""\ MATLAB could not find SPM. Please follow installation instructions at https://en.wikibooks.org/wiki/SPM/Download Make sure to add SPM to MATLAB's path using `startup.m` - """ - ) - ) + """)) return eng diff --git a/tests/__main__.py b/tests/__main__.py index b4ee5a5..87009cf 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -6,9 +6,7 @@ from .conftest import HOME log = logging.getLogger(__name__) -DATA_URL = ( - "https://zenodo.org/record/3877529/files/amyloidPET_FBP_TP0_extra.zip?download=1" -) +DATA_URL = ("https://zenodo.org/record/3877529/files/amyloidPET_FBP_TP0_extra.zip?download=1") def main(): diff --git a/tests/conftest.py b/tests/conftest.py index 8206804..e82ff35 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,14 +12,10 @@ def folder_in(): Ab_PET_mMR_test = HOME / "Ab_PET_mMR_test" if not Ab_PET_mMR_test.is_dir(): skip( - dedent( - """\ + dedent("""\ Cannot find Ab_PET_mMR_test in ${DATA_ROOT:-~} (%s). Try running `python -m tests` to download it. - """ - ) - % HOME - ) + """) % HOME) return Ab_PET_mMR_test @@ -28,15 +24,11 @@ def folder_ref(folder_in): Ab_PET_mMR_ref = folder_in / "testing_reference" / "Ab_PET_mMR_ref" if not Ab_PET_mMR_ref.is_dir(): skip( - dedent( - """\ + dedent("""\ Cannot find Ab_PET_mMR_ref in ${DATA_ROOT:-~}/testing_reference (%s/testing_reference). Try running `python -m tests` to download it. - """ - ) - % HOME - ) + """) % HOME) return Ab_PET_mMR_ref diff --git a/tests/test_regseg.py b/tests/test_regseg.py index 002cc87..60869d1 100644 --- a/tests/test_regseg.py +++ b/tests/test_regseg.py @@ -6,14 +6,9 @@ from spm12 import regseg -MRI2PET = np.array( - [ - [0.99990508, 0.00800995, 0.01121016, -0.68164088], - [-0.00806219, 0.99995682, 0.00462244, -1.16235105], - [-0.01117265, -0.00471238, 0.99992648, -1.02167229], - [0.0, 0.0, 0.0, 1.0], - ] -) +MRI2PET = np.array([[0.99990508, 0.00800995, 0.01121016, -0.68164088], + [-0.00806219, 0.99995682, 0.00462244, -1.16235105], + [-0.01117265, -0.00471238, 0.99992648, -1.02167229], [0.0, 0.0, 0.0, 1.0]]) no_matlab_warn = mark.filterwarnings("ignore:.*collections.abc:DeprecationWarning") no_scipy_warn = mark.filterwarnings("ignore:numpy.ufunc size changed.*:RuntimeWarning") @@ -22,28 +17,16 @@ def assert_equal_arrays(x, y, nmse_tol=0, denan=True): if denan: x, y = map(np.nan_to_num, (x, y)) if nmse_tol: - if ((x - y) ** 2).mean() / (y ** 2).mean() < nmse_tol: + if ((x - y)**2).mean() / (y**2).mean() < nmse_tol: return elif (x == y).all(): return raise ValueError( - dedent( - """\ + dedent("""\ Unequal arrays:x != y. min/mean/max(std): x: {:.3g}/{:.3g}/{:.3g}({:.3g}) y: {:.3g}/{:.3g}/{:.3g}({:.3g}) - """ - ).format( - x.min(), - x.mean(), - x.max(), - x.std(), - y.min(), - y.mean(), - y.max(), - y.std(), - ) - ) + """).format(x.min(), x.mean(), x.max(), x.std(), y.min(), y.mean(), y.max(), y.std())) @no_scipy_warn