diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6e23de..dc1a8dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,11 +37,17 @@ repos: - 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.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/setup.cfg b/setup.cfg index 83eb199..bc4d994 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,12 +68,24 @@ exclude=tests 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 195fab4..c7bcc77 100644 --- a/spm12/cli.py +++ b/spm12/cli.py @@ -19,9 +19,7 @@ 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) log.info(args) if isinstance(args.command, str): 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 9e8e52e..1566e41 100644 --- a/spm12/utils.py +++ b/spm12/utils.py @@ -12,7 +12,7 @@ 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"] @@ -48,14 +48,11 @@ def mcr_run(*cmd, cache="~/.spm12", version=12, mcr_version=713): return check_output((runner, mcr_root) + cmd).decode("U8").strip() except CalledProcessError as err: raise RuntimeError( - dedent( - """\ + dedent("""\ {} See https://en.wikibooks.org/wiki/SPM/Standalone#Trouble-shooting - """ - ).format(err) - ) + """).format(err)) @lru_cache() @@ -91,15 +88,12 @@ def ensure_spm(name=None, cache="~/.spm12", version=12): 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