Skip to content

Commit 8587405

Browse files
committed
Merge branch 'enh/update-zenodo-sorter' into rel/1.2.3
2 parents f930a40 + eeeb650 commit 8587405

File tree

7 files changed

+126
-110
lines changed

7 files changed

+126
-110
lines changed

Diff for: docker/generate_dockerfiles.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ function generate_base_dockerfile() {
6868
--spm12 version=r7219 \
6969
--env 'LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH' \
7070
--freesurfer version=6.0.0-min \
71+
--dcm2niix version=v1.0.20190902 method=source \
7172
--run 'echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tCjUxNzIKICpDdnVtdkVWM3pUZmcKRlM1Si8yYzFhZ2c0RQoiID4gL29wdC9mcmVlc3VyZmVyLTYuMC4wLW1pbi9saWNlbnNlLnR4dA==" | base64 -d | sh' \
7273
--install afni ants apt-utils bzip2 convert3d file fsl-core \
7374
fsl-mni152-templates fusefat g++ git graphviz make python ruby \
74-
unzip xvfb \
75+
unzip xvfb git-annex-standalone liblzma-dev \
7576
--add-to-entrypoint "source /etc/fsl/fsl.sh && source /etc/afni/afni.sh" \
7677
--env ANTSPATH='/usr/lib/ants' \
7778
PATH='/usr/lib/ants:$PATH' \

Diff for: nipype/info.py

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def get_nipype_gitversion():
182182
]
183183

184184
EXTRA_REQUIRES = {
185+
'data': ['datalad'],
185186
'doc': ['Sphinx>=1.4', 'numpydoc', 'matplotlib', 'pydotplus', 'pydot>=1.2.3'],
186187
'duecredit': ['duecredit'],
187188
'nipy': ['nitime', 'nilearn<0.5.0', 'dipy', 'nipy', 'matplotlib'],

Diff for: nipype/interfaces/afni/model.py

+4
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,10 @@ class RemlfitInputSpec(AFNICommandInputSpec):
541541
desc='turns on more progress messages, including memory usage '
542542
'progress reports at various stages',
543543
argstr='-verb')
544+
goforit = traits.Bool(
545+
desc='With potential issues flagged in the design matrix, an attempt '
546+
'will nevertheless be made to fit the model',
547+
argstr='-GOFORIT')
544548
ovar = File(
545549
desc='dataset for OLSQ st.dev. parameter (kind of boring)',
546550
argstr='-Ovar %s')

Diff for: nipype/interfaces/dcm2nii.py

+50-40
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import os
88
import re
99
from copy import deepcopy
10+
import itertools as it
11+
from glob import iglob
1012

1113
from ..utils.filemanip import split_filename
1214
from .base import (CommandLine, CommandLineInputSpec, InputMultiPath, traits,
@@ -328,7 +330,7 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
328330
False,
329331
argstr='-t',
330332
usedefault=True,
331-
desc="Flag if text notes include private patient details")
333+
desc="Text notes including private patient details")
332334
compression = traits.Enum(
333335
1, 2, 3, 4, 5, 6, 7, 8, 9,
334336
argstr='-%d',
@@ -346,6 +348,9 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
346348
philips_float = traits.Bool(
347349
argstr='-p',
348350
desc="Philips precise float (not display) scaling")
351+
to_nrrd = traits.Bool(
352+
argstr="-e",
353+
desc="Export as NRRD instead of NIfTI")
349354

350355

351356
class Dcm2niixOutputSpec(TraitedSpec):
@@ -393,8 +398,11 @@ def version(self):
393398
return Info.version()
394399

395400
def _format_arg(self, opt, spec, val):
396-
bools = ['bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop',
397-
'has_private', 'anon_bids', 'ignore_deriv', 'philips_float']
401+
bools = [
402+
'bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop',
403+
'has_private', 'anon_bids', 'ignore_deriv', 'philips_float',
404+
'to_nrrd',
405+
]
398406
if opt in bools:
399407
spec = deepcopy(spec)
400408
if val:
@@ -410,52 +418,54 @@ def _run_interface(self, runtime):
410418
# may use return code 1 despite conversion
411419
runtime = super(Dcm2niix, self)._run_interface(
412420
runtime, correct_return_codes=(0, 1, ))
413-
if self.inputs.bids_format:
414-
(self.output_files, self.bvecs, self.bvals,
415-
self.bids) = self._parse_stdout(runtime.stdout)
416-
else:
417-
(self.output_files, self.bvecs, self.bvals) = self._parse_stdout(
418-
runtime.stdout)
421+
self._parse_files(self._parse_stdout(runtime.stdout))
419422
return runtime
420423

421424
def _parse_stdout(self, stdout):
422-
files = []
423-
bvecs = []
424-
bvals = []
425-
bids = []
426-
skip = False
427-
find_b = False
425+
filenames = []
428426
for line in stdout.split("\n"):
429-
if not skip:
430-
out_file = None
431-
if line.startswith("Convert "): # output
432-
fname = str(re.search('\S+/\S+', line).group(0))
433-
out_file = os.path.abspath(fname)
434-
# extract bvals
435-
if find_b:
436-
bvecs.append(out_file + ".bvec")
437-
bvals.append(out_file + ".bval")
438-
find_b = False
439-
# next scan will have bvals/bvecs
440-
elif 'DTI gradients' in line or 'DTI gradient directions' in line or 'DTI vectors' in line:
441-
find_b = True
442-
if out_file:
443-
ext = '.nii' if self.inputs.compress == 'n' else '.nii.gz'
444-
files.append(out_file + ext)
445-
if self.inputs.bids_format:
446-
bids.append(out_file + ".json")
447-
skip = False
448-
# just return what was done
449-
if not bids:
450-
return files, bvecs, bvals
427+
if line.startswith("Convert "): # output
428+
fname = str(re.search(r'\S+/\S+', line).group(0))
429+
filenames.append(os.path.abspath(fname))
430+
return filenames
431+
432+
def _parse_files(self, filenames):
433+
outfiles, bvals, bvecs, bids = [], [], [], []
434+
outtypes = [".bval", ".bvec", ".json", ".txt"]
435+
if self.inputs.to_nrrd:
436+
outtypes += [".nrrd", ".nhdr", ".raw.gz"]
451437
else:
452-
return files, bvecs, bvals, bids
438+
outtypes += [".nii", ".nii.gz"]
439+
440+
for filename in filenames:
441+
# search for relevant files, and sort accordingly
442+
for fl in search_files(filename, outtypes):
443+
if (
444+
fl.endswith(".nii") or
445+
fl.endswith(".gz") or
446+
fl.endswith(".nrrd") or
447+
fl.endswith(".nhdr")
448+
):
449+
outfiles.append(fl)
450+
elif fl.endswith(".bval"):
451+
bvals.append(fl)
452+
elif fl.endswith(".bvec"):
453+
bvecs.append(fl)
454+
elif fl.endswith(".json") or fl.endswith(".txt"):
455+
bids.append(fl)
456+
self.output_files = outfiles
457+
self.bvecs = bvecs
458+
self.bvals = bvals
459+
self.bids = bids
453460

454461
def _list_outputs(self):
455462
outputs = self.output_spec().get()
456463
outputs['converted_files'] = self.output_files
457464
outputs['bvecs'] = self.bvecs
458465
outputs['bvals'] = self.bvals
459-
if self.inputs.bids_format:
460-
outputs['bids'] = self.bids
466+
outputs['bids'] = self.bids
461467
return outputs
468+
469+
# https://stackoverflow.com/a/4829130
470+
def search_files(prefix, outtypes):
471+
return it.chain.from_iterable(iglob(prefix + outtype) for outtype in outtypes)

Diff for: nipype/interfaces/tests/test_auto_Dcm2niix.py

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def test_Dcm2niix_inputs():
6161
position=-1,
6262
xor=['source_dir'],
6363
),
64+
to_nrrd=dict(argstr='-e', ),
6465
verbose=dict(
6566
argstr='-v',
6667
usedefault=True,

Diff for: nipype/interfaces/tests/test_extra_dcm2nii.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,15 @@ def assert_dwi(eg, bids):
4141
# ensure all outputs are of equal lengths
4242
assert len(set(map(len, outputs))) == 1
4343
else:
44-
assert not eg2.outputs.bids
44+
assert not eg.outputs.bids
4545

4646
dcm = Dcm2niix()
4747
dcm.inputs.source_dir = datadir
4848
dcm.inputs.out_filename = '%u%z'
49-
eg1 = dcm.run()
50-
assert_dwi(eg1, True)
49+
assert_dwi(dcm.run(), True)
5150

5251
# now run specifying output directory and removing BIDS option
5352
outdir = tmpdir.mkdir('conversion').strpath
5453
dcm.inputs.output_dir = outdir
5554
dcm.inputs.bids_format = False
56-
eg2 = dcm.run()
57-
assert_dwi(eg2, False)
55+
assert_dwi(dcm.run(), False)

Diff for: tools/update_zenodo.py

+65-64
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,16 @@
11
#!/usr/bin/env python3
2+
"""Update and sort the creators list of the zenodo record."""
3+
import sys
4+
import shutil
5+
from pathlib import Path
26
import json
37
from fuzzywuzzy import fuzz, process
4-
import shutil
5-
import os
68
import subprocess as sp
79

8-
if os.path.exists('line-contributions.txt'):
9-
with open('line-contributions.txt', 'rt') as fp:
10-
lines = fp.readlines()
11-
else:
12-
if shutil.which('git-line-summary'):
13-
print("Running git-line-summary on nipype repo")
14-
lines = sp.check_output(['git-line-summary']).decode().split('\n')
15-
else:
16-
raise RuntimeError("Install Git Extras to view git contributors")
17-
18-
data = [' '.join(line.strip().split()[1:-1]) for line in lines if '%' in line]
19-
20-
# load zenodo from master
21-
with open('.zenodo.json', 'rt') as fp:
22-
zenodo = json.load(fp)
23-
zen_names = [' '.join(val['name'].split(',')[::-1]).strip()
24-
for val in zenodo['creators']]
25-
26-
name_matches = []
27-
28-
for ele in data:
29-
matches = process.extract(ele, zen_names, scorer=fuzz.token_sort_ratio,
30-
limit=2)
31-
# matches is a list [('First match', % Match), ('Second match', % Match)]
32-
if matches[0][1] > 80:
33-
val = zenodo['creators'][zen_names.index(matches[0][0])]
34-
else:
35-
# skip unmatched names
36-
print("No entry to sort:", ele)
37-
continue
38-
39-
if val not in name_matches:
40-
name_matches.append(val)
41-
10+
# These ORCIDs should go last
11+
CREATORS_LAST = ['Gorgolewski, Krzysztof J.', 'Ghosh, Satrajit']
4212
# for entries not found in line-contributions
43-
missing_entries = [
13+
MISSING_ENTRIES = [
4414
{"name": "Varada, Jan"},
4515
{"name": "Schwabacher, Isaac"},
4616
{"affiliation": "Child Mind Institute / Nathan Kline Institute",
@@ -60,31 +30,62 @@
6030
{"name": "Lai, Jeff"}
6131
]
6232

63-
for entry in missing_entries:
64-
name_matches.append(entry)
33+
if __name__ == '__main__':
34+
contrib_file = Path('line-contributors.txt')
35+
lines = []
36+
if contrib_file.exists():
37+
print('WARNING: Reusing existing line-contributors.txt file.', file=sys.stderr)
38+
lines = contrib_file.read_text().splitlines()
6539

66-
67-
def fix_position(creators):
68-
# position first / last authors
69-
f_authr = None
70-
l_authr = None
71-
72-
for i, info in enumerate(creators):
73-
if info['name'] == 'Gorgolewski, Krzysztof J.':
74-
f_authr = i
75-
if info['name'] == 'Ghosh, Satrajit':
76-
l_authr = i
77-
78-
if f_authr is None or l_authr is None:
79-
raise AttributeError('Missing important people')
80-
81-
creators.insert(0, creators.pop(f_authr))
82-
creators.insert(len(creators), creators.pop(l_authr + 1))
83-
return creators
84-
85-
86-
zenodo['creators'] = fix_position(name_matches)
87-
88-
with open('.zenodo.json', 'wt') as fp:
89-
json.dump(zenodo, fp, indent=2, sort_keys=True)
90-
fp.write('\n')
40+
git_line_summary_path = shutil.which('git-line-summary')
41+
if not lines and git_line_summary_path:
42+
print("Running git-line-summary on nipype repo")
43+
lines = sp.check_output([git_line_summary_path]).decode().splitlines()
44+
contrib_file.write_text('\n'.join(lines))
45+
46+
if not lines:
47+
raise RuntimeError("""\
48+
Could not find line-contributors from git repository.%s""" % """ \
49+
git-line-summary not found, please install git-extras. """ * (git_line_summary_path is None))
50+
51+
data = [' '.join(line.strip().split()[1:-1]) for line in lines if '%' in line]
52+
53+
# load zenodo from master
54+
zenodo_file = Path('.zenodo.json')
55+
zenodo = json.loads(zenodo_file.read_text())
56+
zen_names = [' '.join(val['name'].split(',')[::-1]).strip()
57+
for val in zenodo['creators']]
58+
total_names = len(zen_names) + len(MISSING_ENTRIES)
59+
60+
name_matches = []
61+
position = 1
62+
for ele in data:
63+
matches = process.extract(ele, zen_names, scorer=fuzz.token_sort_ratio,
64+
limit=2)
65+
# matches is a list [('First match', % Match), ('Second match', % Match)]
66+
if matches[0][1] > 80:
67+
val = zenodo['creators'][zen_names.index(matches[0][0])]
68+
else:
69+
# skip unmatched names
70+
print("No entry to sort:", ele)
71+
continue
72+
73+
if val not in name_matches:
74+
if val['name'] not in CREATORS_LAST:
75+
val['position'] = position
76+
position += 1
77+
else:
78+
val['position'] = total_names + CREATORS_LAST.index(val['name'])
79+
name_matches.append(val)
80+
81+
for missing in MISSING_ENTRIES:
82+
missing['position'] = position
83+
position += 1
84+
name_matches.append(missing)
85+
86+
zenodo['creators'] = sorted(name_matches, key=lambda k: k['position'])
87+
# Remove position
88+
for creator in zenodo['creators']:
89+
del creator['position']
90+
91+
zenodo_file.write_text('%s\n' % json.dumps(zenodo, indent=2, sort_keys=True))

0 commit comments

Comments
 (0)