Skip to content

Commit dedd6f9

Browse files
committed
fix: remove dcm2niix terminal output input
2 parents a5de8ae + 35e95b6 commit dedd6f9

File tree

16 files changed

+246
-86
lines changed

16 files changed

+246
-86
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ install:
3636
- git config --global user.name "Travis Almighty"
3737

3838
script:
39-
- coverage run `which py.test` -s -v tests heuristics
39+
- coverage run `which py.test` -s -v tests heuristics/*.py
4040

4141
after_success:
4242
- codecov

heudiconv/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# set logger handler
22
import logging
3-
logging.getLogger(__name__).addHandler(logging.NullHandler())
4-
3+
import os
54
from .info import (__version__, __packagename__)
5+
6+
# Rudimentary logging support.
7+
lgr = logging.getLogger(__name__)
8+
logging.basicConfig(
9+
format='%(levelname)s: %(message)s',
10+
level=getattr(logging, os.environ.get('HEUDICONV_LOG_LEVEL', 'INFO'))
11+
)
12+
lgr.debug("Starting the abomination") # just to "run-test" logging

heudiconv/bids.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ def populate_bids_templates(path, defaults={}):
6161
create_file_if_missing(op.join(path, 'CHANGES'),
6262
"0.0.1 Initial data acquired\n"
6363
"TODOs:\n\t- verify and possibly extend information in participants.tsv"
64-
"(see for example http://datasets.datalad.org/?dir=/openfmri/ds000208)"
65-
"\n\t- fill out dataset_description.json, README, sourcedata/README "
66-
"(if present)\n\t- provide _events.tsv file for each _bold.nii.gz with "
67-
"onsets of events (see '8.5 Task events' of BIDS specification)")
64+
" (see for example http://datasets.datalad.org/?dir=/openfmri/ds000208)"
65+
"\n\t- fill out dataset_description.json, README, sourcedata/README"
66+
" (if present)\n\t- provide _events.tsv file for each _bold.nii.gz with"
67+
" onsets of events (see '8.5 Task events' of BIDS specification)")
6868
create_file_if_missing(op.join(path, 'README'),
6969
"TODO: Provide description for the dataset -- basic details about the "
7070
"study, possibly pointing to pre-registration (if public or embargoed)")
@@ -92,7 +92,7 @@ def populate_bids_templates(path, defaults={}):
9292
events_file = fpath[:-len(suf)] + '_events.tsv'
9393
lgr.debug("Generating %s", events_file)
9494
with open(events_file, 'w') as f:
95-
f.write("onset\tduration\ttrial_type\tresponse_time\tTODO -- fill in rows and add more tab-separated columns if desired")
95+
f.write("onset\tduration\ttrial_type\tresponse_time\tstim_file\tTODO -- fill in rows and add more tab-separated columns if desired")
9696
# extract tasks files stubs
9797
for task_acq, fields in tasks.items():
9898
task_file = op.join(path, task_acq + '_bold.json')

heudiconv/cli/run.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ..bids import populate_bids_templates, tuneup_bids_json_files
1111
from ..queue import queue_conversion
1212

13+
from ..bids import (populate_bids_templates, tuneup_bids_json_files)
1314
import inspect
1415
import logging
1516
lgr = logging.getLogger(__name__)
@@ -89,11 +90,20 @@ def process_extra_commands(outdir, args):
8990
def main(argv=None):
9091
parser = get_parser()
9192
args = parser.parse_args(argv)
93+
# To be done asap so anything random is deterministic
94+
if args.random_seed is not None:
95+
import random
96+
random.seed(args.random_seed)
97+
import numpy
98+
numpy.random.seed(args.random_seed)
9299
if args.debug:
93100
lgr.setLevel(logging.DEBUG)
94-
95-
if args.files and args.subjs:
96-
raise ValueError("Unable to processes `--subjects` with files")
101+
# Should be possible but only with a single subject -- will be used to
102+
# override subject deduced from the DICOMs
103+
if args.files and args.subjs and len(args.subjs) > 1:
104+
raise ValueError(
105+
"Unable to processes multiple `--subjects` with files"
106+
)
97107

98108
if args.debug:
99109
setup_exceptionhook()
@@ -121,9 +131,10 @@ def get_parser():
121131
help='list of subjects - required for dicom template. '
122132
'If not provided, DICOMS would first be "sorted" and '
123133
'subject IDs deduced by the heuristic')
124-
parser.add_argument('-c', '--converter', default='dcm2niix',
134+
parser.add_argument('-c', '--converter',
135+
default='dcm2niix',
125136
choices=('dcm2niix', 'none'),
126-
help='tool to use for dicom conversion. Setting to '
137+
help='tool to use for DICOM conversion. Setting to '
127138
'"none" disables the actual conversion step -- useful'
128139
'for testing heuristics.')
129140
parser.add_argument('-o', '--outdir', default=os.getcwd(),
@@ -151,7 +162,7 @@ def get_parser():
151162
parser.add_argument('-b', '--bids', action='store_true',
152163
help='flag for output into BIDS structure')
153164
parser.add_argument('--overwrite', action='store_true', default=False,
154-
help='flag to allow overwrite existing files')
165+
help='flag to allow overwriting existing converted files')
155166
parser.add_argument('--datalad', action='store_true',
156167
help='Store the entire collection as DataLad '
157168
'dataset(s). Small files will be committed directly to '
@@ -173,7 +184,8 @@ def get_parser():
173184
parser.add_argument('--minmeta', action='store_true',
174185
help='Exclude dcmstack meta information in sidecar '
175186
'jsons')
176-
187+
parser.add_argument('--random-seed', type=int, default=None,
188+
help='Random seed to initialize RNG')
177189
submission = parser.add_argument_group('Conversion submission options')
178190
submission.add_argument('-q', '--queue', default=None,
179191
help='select batch system to submit jobs to instead'
@@ -217,6 +229,9 @@ def process_args(args):
217229

218230
for (locator, session, sid), files_or_seqinfo in study_sessions.items():
219231

232+
# Allow for session to be overloaded from command line
233+
if args.session is not None:
234+
session = args.session
220235
if not len(files_or_seqinfo):
221236
raise ValueError("nothing to process?")
222237
# that is how life is ATM :-/ since we don't do sorting if subj
@@ -252,7 +267,7 @@ def process_args(args):
252267
sid,
253268
args.anon_cmd,
254269
args.converter,
255-
args.session,
270+
session,
256271
args.with_prov,
257272
args.bids)
258273
continue
@@ -271,10 +286,10 @@ def process_args(args):
271286
from ..external.dlad import prepare_datalad
272287
dlad_sid = sid if not anon_sid else anon_sid
273288
dl_msg = prepare_datalad(anon_study_outdir, anon_outdir, dlad_sid,
274-
args.session, seqinfo, dicoms, args.bids)
289+
session, seqinfo, dicoms, args.bids)
275290

276291
lgr.info("PROCESSING STARTS: {0}".format(
277-
str(dict(subject=sid, outdir=study_outdir, session=args.session))))
292+
str(dict(subject=sid, outdir=study_outdir, session=session))))
278293

279294
prep_conversion(sid,
280295
dicoms,
@@ -284,7 +299,7 @@ def process_args(args):
284299
anon_sid=anon_sid,
285300
anon_outdir=anon_study_outdir,
286301
with_prov=args.with_prov,
287-
ses=args.session,
302+
ses=session,
288303
bids=args.bids,
289304
seqinfo=seqinfo,
290305
min_meta=args.minmeta,

heudiconv/convert.py

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
set_readonly,
1616
clear_temp_dicoms,
1717
seqinfo_fields,
18+
assure_no_file_exists,
19+
file_md5sum
1820
)
1921
from .bids import (
2022
convert_sid_bids,
@@ -102,15 +104,35 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
102104
if not op.exists(idir):
103105
os.makedirs(idir)
104106

105-
shutil.copy(heuristic.filename, idir)
106107
ses_suffix = "_ses-%s" % ses if ses is not None else ""
107108
info_file = op.join(idir, '%s%s.auto.txt' % (sid, ses_suffix))
108109
edit_file = op.join(idir, '%s%s.edit.txt' % (sid, ses_suffix))
109110
filegroup_file = op.join(idir, 'filegroup%s.json' % ses_suffix)
110111

112+
# if conversion table(s) do not exist -- we need to prepare them
113+
# (the *prepare* stage in https://github.com/nipy/heudiconv/issues/134)
114+
reuse_conversion_table = op.exists(edit_file)
115+
# We also might need to redo it if changes in the heuristic file
116+
# detected
117+
# ref: https://github.com/nipy/heudiconv/issues/84#issuecomment-330048609
118+
# for more automagical wishes
119+
target_heuristic_filename = op.join(idir, op.basename(heuristic.filename))
120+
# TODO:
121+
# 1. add a test
122+
# 2. possibly extract into a dedicated function for easier logic flow here
123+
# and a dedicated unittest
124+
if not reuse_conversion_table and \
125+
op.exists(target_heuristic_filename) and \
126+
file_md5sum(target_heuristic_filename) != file_md5sum(heuristic.filename):
127+
reuse_conversion_table = False
128+
lgr.info(
129+
"Will not reuse existing conversion table files because heuristic "
130+
"has changed"
131+
)
132+
111133
# MG - maybe add an option to force rerun?
112134
# related issue : https://github.com/nipy/heudiconv/issues/84
113-
if op.exists(edit_file) and overwrite:
135+
if reuse_conversion_table:
114136
lgr.info("Reloading existing filegroup.json "
115137
"because %s exists", edit_file)
116138
info = read_config(edit_file)
@@ -124,6 +146,8 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
124146
else:
125147
# TODO -- might have been done outside already!
126148
# MG -- will have to try with both dicom template, files
149+
assure_no_file_exists(target_heuristic_filename)
150+
safe_copyfile(heuristic.filename, idir)
127151
if dicoms:
128152
seqinfo = group_dicoms_into_seqinfos(
129153
dicoms,
@@ -133,6 +157,8 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
133157
seqinfo_list = list(seqinfo.keys())
134158
filegroup = {si.series_id: x for si, x in seqinfo.items()}
135159
dicominfo_file = op.join(idir, 'dicominfo%s.tsv' % ses_suffix)
160+
# allow to overwrite even if was present under git-annex already
161+
assure_no_file_exists(dicominfo_file)
136162
with open(dicominfo_file, 'wt') as fp:
137163
fp.write('\t'.join([val for val in seqinfo_fields]) + '\n')
138164
for seq in seqinfo_list:
@@ -141,7 +167,9 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
141167
info = heuristic.infotodict(seqinfo_list)
142168
lgr.debug("Writing to {}, {}, {}".format(info_file, edit_file,
143169
filegroup_file))
170+
assure_no_file_exists(info_file)
144171
write_config(info_file, info)
172+
assure_no_file_exists(edit_file)
145173
write_config(edit_file, info)
146174
save_json(filegroup_file, filegroup)
147175

@@ -152,7 +180,7 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
152180
else:
153181
tdir = op.join(anon_outdir, anon_sid)
154182

155-
if converter != 'none':
183+
if converter.lower() != 'none':
156184
lgr.info("Doing conversion using %s", converter)
157185
cinfo = conversion_info(anon_sid, tdir, info, filegroup, ses)
158186
convert(cinfo,
@@ -220,8 +248,8 @@ def convert(items, converter, scaninfo_suffix, custom_callable, with_prov,
220248
os.makedirs(prefix_dirname)
221249

222250
for outtype in outtypes:
223-
lgr.debug("Processing %d dicoms for output type %s",
224-
len(item_dicoms), outtype)
251+
lgr.debug("Processing %d dicoms for output type %s. Overwrite=%s",
252+
len(item_dicoms), outtype, overwrite)
225253
lgr.debug("Includes the following dicoms: %s", item_dicoms)
226254

227255
seqtype = op.basename(op.dirname(prefix)) if bids else None
@@ -245,7 +273,8 @@ def convert(items, converter, scaninfo_suffix, custom_callable, with_prov,
245273

246274
bids_outfiles = save_converted_files(res, item_dicoms, bids,
247275
outtype, prefix,
248-
outname_bids)
276+
outname_bids,
277+
overwrite=overwrite)
249278

250279
# save acquisition time information if it's BIDS
251280
# at this point we still have acquisition date
@@ -258,11 +287,18 @@ def convert(items, converter, scaninfo_suffix, custom_callable, with_prov,
258287
prov_files.append(prov_file)
259288

260289
tempdirs.rmtree(tmpdir)
290+
else:
291+
raise RuntimeError(
292+
"was asked to convert into %s but destination already exists"
293+
% (outname)
294+
)
261295

262296
if len(bids_outfiles) > 1:
263297
lgr.warning("For now not embedding BIDS and info generated "
264298
".nii.gz itself since sequence produced "
265299
"multiple files")
300+
elif not bids_outfiles:
301+
lgr.debug("No BIDS files were produced, nothing to embed to then")
266302
else:
267303
embed_metadata_from_dicoms(bids, item_dicoms, outname, outname_bids,
268304
prov_file, scaninfo, tempdirs, with_prov,
@@ -330,10 +366,11 @@ def convert_dicom(item_dicoms, bids, prefix,
330366
outfile = op.join(dicomdir, op.basename(filename))
331367
if not op.islink(outfile):
332368
# TODO: add option to enable hardlink?
333-
if symlink:
334-
os.symlink(filename, outfile)
335-
else:
336-
os.link(filename, outfile)
369+
# if symlink:
370+
# os.symlink(filename, outfile)
371+
# else:
372+
# os.link(filename, outfile)
373+
shutil.copyfile(filename, outfile)
337374

338375

339376
def nipype_convert(item_dicoms, prefix, with_prov, bids, tmpdir):
@@ -350,7 +387,6 @@ def nipype_convert(item_dicoms, prefix, with_prov, bids, tmpdir):
350387
convertnode.base_dir = tmpdir
351388
convertnode.inputs.source_names = item_dicoms
352389
convertnode.inputs.out_filename = op.basename(op.dirname(prefix))
353-
convertnode.inputs.terminal_output = 'allatonce'
354390
convertnode.inputs.bids_format = bids
355391
eg = convertnode.run()
356392

@@ -365,7 +401,7 @@ def nipype_convert(item_dicoms, prefix, with_prov, bids, tmpdir):
365401
return eg, prov_file
366402

367403

368-
def save_converted_files(res, item_dicoms, bids, outtype, prefix, outname_bids):
404+
def save_converted_files(res, item_dicoms, bids, outtype, prefix, outname_bids, overwrite):
369405
"""Copy converted files from tempdir to output directory.
370406
Will rename files if necessary.
371407
@@ -396,8 +432,8 @@ def save_converted_files(res, item_dicoms, bids, outtype, prefix, outname_bids):
396432

397433
if isdefined(res.outputs.bvecs) and isdefined(res.outputs.bvals):
398434
outname_bvecs, outname_bvals = prefix + '.bvec', prefix + '.bval'
399-
safe_copyfile(res.outputs.bvecs, outname_bvecs)
400-
safe_copyfile(res.outputs.bvals, outname_bvals)
435+
safe_copyfile(res.outputs.bvecs, outname_bvecs, overwrite)
436+
safe_copyfile(res.outputs.bvals, outname_bvals, overwrite)
401437

402438
if isinstance(res_files, list):
403439
# we should provide specific handling for fmap,
@@ -421,18 +457,18 @@ def save_converted_files(res, item_dicoms, bids, outtype, prefix, outname_bids):
421457

422458
for fl, suffix, bids_file in zip(res_files, suffixes, bids_files):
423459
outname = "%s%s.%s" % (prefix, suffix, outtype)
424-
safe_copyfile(fl, outname)
460+
safe_copyfile(fl, outname, overwrite)
425461
if bids_file:
426462
outname_bids_file = "%s%s.json" % (prefix, suffix)
427-
safe_copyfile(bids_file, outname_bids_file)
463+
safe_copyfile(bids_file, outname_bids_file, overwrite)
428464
bids_outfiles.append(outname_bids_file)
429465
# res_files is not a list
430466
else:
431467
outname = "{}.{}".format(prefix, outtype)
432-
safe_copyfile(res_files, outname)
468+
safe_copyfile(res_files, outname, overwrite)
433469
if isdefined(res.outputs.bids):
434470
try:
435-
safe_copyfile(res.outputs.bids, outname_bids)
471+
safe_copyfile(res.outputs.bids, outname_bids, overwrite)
436472
bids_outfiles.append(outname_bids)
437473
except TypeError as exc: ##catch lists
438474
raise TypeError("Multiple BIDS sidecars detected.")

heudiconv/dicoms.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,7 @@ def group_dicoms_into_seqinfos(files, file_filter, dcmfilter, grouping):
185185
except AttributeError:
186186
series_desc = ''
187187

188-
motion_corrected = ('moco' in dcminfo.SeriesDescription.lower()
189-
or 'MOCO' in image_type)
188+
motion_corrected = 'MOCO' in image_type
190189

191190
if dcminfo.get([0x18,0x24], None):
192191
# GE and Philips scanners
@@ -448,6 +447,9 @@ def embed_metadata_from_dicoms(bids, item_dicoms, outname, outname_bids,
448447
from nipype import Node, Function
449448
tmpdir = tempdirs(prefix='embedmeta')
450449

450+
# We need to assure that paths are absolute if they are relative
451+
item_dicoms = list(map(op.abspath, item_dicoms))
452+
451453
embedfunc = Node(Function(input_names=['dcmfiles', 'niftifile', 'infofile',
452454
'bids_info', 'force', 'min_meta'],
453455
output_names=['outfile', 'meta'],
@@ -464,6 +466,8 @@ def embed_metadata_from_dicoms(bids, item_dicoms, outname, outname_bids,
464466
embedfunc.inputs.force = True
465467
embedfunc.base_dir = tmpdir
466468
cwd = os.getcwd()
469+
lgr.debug("Embedding into %s based on dicoms[0]=%s for nifti %s",
470+
scaninfo, item_dicoms[0], outname)
467471
try:
468472
if op.lexists(scaninfo):
469473
# TODO: handle annexed file case

heudiconv/external/dlad.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def add_to_datalad(topdir, studydir, msg, bids):
5151
ds_ = create(curdir_, dataset=superds,
5252
force=True,
5353
no_annex=True,
54-
shared_access='all',
54+
# shared_access='all',
5555
annex_version=6)
5656
assert ds == ds_
5757
assert ds.is_installed()
@@ -94,8 +94,9 @@ def add_to_datalad(topdir, studydir, msg, bids):
9494
"yet provided", ds)
9595
else:
9696
dsh = ds.create(path='.heudiconv',
97-
force=True,
98-
shared_access='all')
97+
force=True
98+
# shared_access='all'
99+
)
99100
# Since .heudiconv could contain sensitive information
100101
# we place all files under annex and then add
101102
if create_file_if_missing(op.join(dsh_path, '.gitattributes'),

0 commit comments

Comments
 (0)