Skip to content

Commit 76cb54e

Browse files
committed
Merge tag 'v0.5.1' into debian
Heudiconv v0.5.1 * tag 'v0.5.1': (35 commits) rel: proper release day Fix links to heuristcs in README.md doc+enh: update docker dcm2niix, add entry to changelog doc: changelog + version bump fix: do not load phasediff until exists BF: workaround for elderly buggy python 2.7 exec BF: for datalad 0.10.0 - check/populate .gitattributes correctly BF+ENH: set dist-restrictions metadata correctly Implement renaming suggested by @yarikoptic Provide SeriesInstanceUID in SeqInfo enh: update docker build with latest dcm2niix release tst: add check for running with no heuristic fix: syntax fix: simply regenerate conversion table if heurisitic has changed fix: various small fixes fix multiple conversion #196 BF: Don't create a sourcedata-dir just for a README; Check lexists for the actual file instead BF: Do not overwrite existing files with BIDS templates DOC: Point to James' youtube video ENH: print pip version right before installing dev-requirements ...
2 parents c5cab90 + 95b9f73 commit 76cb54e

23 files changed

+466
-174
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ before_install:
2525
- pip install --upgrade virtualenv
2626
- virtualenv --python=python venv
2727
- source venv/bin/activate
28+
- pip --version # check again since seems that python_requires='>=3.5' in secretstorage is not in effect
2829
- python --version # just to check
2930
- pip install -r dev-requirements.txt
3031
- pip install requests # below installs pyld but that assumes we have requests already

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@ All notable changes to this project will be documented (for humans) in this file
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [0.5.1] - 2018-07-05
8+
Bugfix release
9+
10+
### Added
11+
- Video tutorial / updated slides
12+
- Helper to set metadata restrictions correctly
13+
- Usage is now shown when run without arguments
14+
- New fields to Seqinfo
15+
- series_uid
16+
- Reproin heuristic support for xnat
17+
### Changed
18+
- Dockerfile updated to use `dcm2niix v1.0.20180622`
19+
- Conversion table will be regenerated if heurisic has changed
20+
- Do not touch existing BIDS files
21+
- events.tsv
22+
- task JSON
23+
### Fixed
24+
- Python 2.7.8 and older installation
25+
- Support for updated packages
26+
- `Datalad` 0.10
27+
- `pydicom` 1.0.2
28+
- Later versions of `pydicom` are prioritized first
29+
- JSON pretty print should not remove spaces
30+
- Phasediff fieldmaps behavior
31+
- ensure phasediff exists
32+
- support for single magnitude acquisitions
33+
734
## [0.5] - 2018-03-01
835
The first release after major refactoring:
936

Dockerfile

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# pull request on our GitHub repository:
66
# https://github.com/kaczmarj/neurodocker
77
#
8-
# Timestamp: 2018-02-02 16:32:55
8+
# Timestamp: 2018-06-29 18:14:08
99

1010
FROM debian:stretch
1111

@@ -32,27 +32,48 @@ RUN apt-get update -qq && apt-get install -yq --no-install-recommends \
3232
&& chmod -R 777 /neurodocker && chmod a+s /neurodocker
3333
ENTRYPOINT ["/neurodocker/startup.sh"]
3434

35-
RUN apt-get update -qq \
36-
&& apt-get install -y -q --no-install-recommends git \
37-
gcc \
38-
pigz \
39-
&& apt-get clean \
40-
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
41-
4235
#------------------------
43-
# Install dcm2niix v1.0.20171215
36+
# Install dcm2niix v1.0.20180622
4437
#------------------------
4538
WORKDIR /tmp
4639
RUN deps='cmake g++ gcc git make pigz zlib1g-dev' \
4740
&& apt-get update -qq && apt-get install -yq --no-install-recommends $deps \
4841
&& apt-get clean \
4942
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
5043
&& mkdir dcm2niix \
51-
&& curl -sSL https://github.com/rordenlab/dcm2niix/tarball/v1.0.20171215 | tar xz -C dcm2niix --strip-components 1 \
44+
&& curl -sSL https://github.com/rordenlab/dcm2niix/tarball/v1.0.20180622 | tar xz -C dcm2niix --strip-components 1 \
5245
&& mkdir dcm2niix/build && cd dcm2niix/build \
5346
&& cmake .. && make \
5447
&& make install \
55-
&& rm -rf /tmp/*
48+
&& rm -rf /tmp/* \
49+
&& apt-get purge -y --auto-remove $deps
50+
51+
#--------------------------------------------------
52+
# Add NeuroDebian repository
53+
# Please note that some packages downloaded through
54+
# NeuroDebian may have restrictive licenses.
55+
#--------------------------------------------------
56+
RUN apt-get update -qq && apt-get install -yq --no-install-recommends dirmngr gnupg \
57+
&& apt-get clean \
58+
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
59+
&& curl -sSL http://neuro.debian.net/lists/stretch.us-nh.full \
60+
> /etc/apt/sources.list.d/neurodebian.sources.list \
61+
&& curl -sSL https://dl.dropbox.com/s/zxs209o955q6vkg/neurodebian.gpg \
62+
| apt-key add - \
63+
&& (apt-key adv --refresh-keys --keyserver hkp://pool.sks-keyservers.net:80 0xA5D32F012649A5A9 || true) \
64+
&& apt-get update
65+
66+
# Install NeuroDebian packages
67+
RUN apt-get update -qq && apt-get install -yq --no-install-recommends git-annex-standalone \
68+
&& apt-get clean \
69+
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
70+
71+
RUN apt-get update -qq \
72+
&& apt-get install -y -q --no-install-recommends git \
73+
gcc \
74+
pigz \
75+
&& apt-get clean \
76+
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
5677

5778
COPY [".", "/src/heudiconv"]
5879

@@ -83,26 +104,6 @@ RUN conda create -y -q --name neuro python=2 \
83104
&& sync \
84105
&& sed -i '$isource activate neuro' $ND_ENTRYPOINT
85106

86-
#--------------------------------------------------
87-
# Add NeuroDebian repository
88-
# Please note that some packages downloaded through
89-
# NeuroDebian may have restrictive licenses.
90-
#--------------------------------------------------
91-
RUN apt-get update -qq && apt-get install -yq --no-install-recommends dirmngr gnupg \
92-
&& apt-get clean \
93-
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
94-
&& curl -sSL http://neuro.debian.net/lists/stretch.us-nh.full \
95-
> /etc/apt/sources.list.d/neurodebian.sources.list \
96-
&& curl -sSL https://dl.dropbox.com/s/zxs209o955q6vkg/neurodebian.gpg \
97-
| apt-key add - \
98-
&& (apt-key adv --refresh-keys --keyserver hkp://pool.sks-keyservers.net:80 0xA5D32F012649A5A9 || true) \
99-
&& apt-get update
100-
101-
# Install NeuroDebian packages
102-
RUN apt-get update -qq && apt-get install -yq --no-install-recommends git-annex-standalone \
103-
&& apt-get clean \
104-
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
105-
106107
ENTRYPOINT ["/neurodocker/startup.sh", "heudiconv"]
107108

108109
#--------------------------------------
@@ -117,6 +118,20 @@ RUN echo '{ \
117118
\n "debian:stretch" \
118119
\n ], \
119120
\n [ \
121+
\n "dcm2niix", \
122+
\n { \
123+
\n "version": "v1.0.20180622" \
124+
\n } \
125+
\n ], \
126+
\n [ \
127+
\n "neurodebian", \
128+
\n { \
129+
\n "os_codename": "stretch", \
130+
\n "download_server": "usa-nh", \
131+
\n "pkgs": "git-annex-standalone" \
132+
\n } \
133+
\n ], \
134+
\n [ \
120135
\n "install", \
121136
\n [ \
122137
\n "git", \
@@ -125,12 +140,6 @@ RUN echo '{ \
125140
\n ] \
126141
\n ], \
127142
\n [ \
128-
\n "dcm2niix", \
129-
\n { \
130-
\n "version": "v1.0.20171215" \
131-
\n } \
132-
\n ], \
133-
\n [ \
134143
\n "copy", \
135144
\n [ \
136145
\n ".", \
@@ -142,23 +151,15 @@ RUN echo '{ \
142151
\n { \
143152
\n "env_name": "neuro", \
144153
\n "conda_install": "python=2 traits=4.6.0", \
145-
\n "pip_install": "https://github.com/moloney/dcmstack/tarball/master /src/heudiconv[all]", \
146-
\n "activate": true \
147-
\n } \
148-
\n ], \
149-
\n [ \
150-
\n "neurodebian", \
151-
\n { \
152-
\n "os_codename": "stretch", \
153-
\n "download_server": "usa-nh", \
154-
\n "pkgs": "git-annex-standalone" \
154+
\n "activate": true, \
155+
\n "pip_install": "https://github.com/moloney/dcmstack/tarball/master /src/heudiconv[all]" \
155156
\n } \
156157
\n ], \
157158
\n [ \
158159
\n "entrypoint", \
159160
\n "/neurodocker/startup.sh heudiconv" \
160161
\n ] \
161162
\n ], \
162-
\n "generation_timestamp": "2018-02-02 16:32:55", \
163+
\n "generation_timestamp": "2018-06-29 18:14:08", \
163164
\n "neurodocker_version": "0.3.2" \
164165
\n}' > /neurodocker/neurodocker_specs.json

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ HeuDiConv operates using a heuristic, which provides information on
2323
how your files should be converted. A number of example heuristics are
2424
provided to address various use-cases
2525

26-
- the [cmrr_heuristic](heuristics/cmrr_heuristic.py) provides an
26+
- the [cmrr_heuristic](heudiconv/heuristics/cmrr_heuristic.py) provides an
2727
example for a conversion to [BIDS]
28-
- the [reproin](heuristics/reproin.py) could be used to establish
28+
- the [reproin](heudiconv/heuristics/reproin.py) could be used to establish
2929
a complete imaging center wide automation to convert all acquired
3030
data to [BIDS] following a simple naming
3131
[convention](https://goo.gl/o0YASC) for studies and sequences
@@ -36,7 +36,7 @@ provided to address various use-cases
3636

3737
Released versions of HeuDiConv are available from PyPI so you could
3838
just `pip install heudiconv[all]` for the most complete installation,
39-
and it would require manual installation ony
39+
and it would require manual installation only
4040
of the [dcm2niix](https://github.com/rordenlab/dcm2niix/). On
4141
Debian-based systems we recommend to use
4242
[NeuroDebian](http://neuro.debian.net) providing
@@ -64,14 +64,19 @@ manager appropriate for your OS.
6464
## Tutorial with example conversion to BIDS format using Docker
6565
Please read this tutorial to understand how heudiconv works in practice.
6666

67-
[Slides here](http://nipy.org/workshops/2017-03-boston/lectures/bids-heudiconv/#1)
67+
[Slides here](http://nipy.org/heudiconv/#1)
6868

6969
To generate lean BIDS output, consider using both the `-b` and the `--minmeta` flags
7070
to your heudiconv command. The `-b` flag generates a json file with BIDS keys, while
7171
the `--minmeta` flag restricts the json file to only BIDS keys. Without `--minmeta`,
7272
the json file and the associated Nifti file contains DICOM metadata extracted using
7373
dicomstack.
7474

75+
### Other tutorials
76+
77+
- YouTube:
78+
- ["Heudiconv Example"](https://www.youtube.com/watch?v=O1kZAuR7E00) by [James Kent](https://github.com/jdkent)
79+
7580
## How it works (in some more detail)
7681

7782
Call `heudiconv` like this:

heudiconv/bids.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def populate_bids_templates(path, defaults={}):
2929

3030
lgr.info("Populating template files under %s", path)
3131
descriptor = op.join(path, 'dataset_description.json')
32-
if not op.exists(descriptor):
32+
if not op.lexists(descriptor):
3333
save_json(descriptor,
3434
OrderedDict([
3535
('Name', "TODO: name of the dataset"),
@@ -89,18 +89,22 @@ def populate_bids_templates(path, defaults={}):
8989
suf = '_bold.json'
9090
assert fpath.endswith(suf)
9191
events_file = fpath[:-len(suf)] + '_events.tsv'
92-
lgr.debug("Generating %s", events_file)
93-
with open(events_file, 'w') as f:
94-
f.write("onset\tduration\ttrial_type\tresponse_time\tstim_file\tTODO -- fill in rows and add more tab-separated columns if desired")
92+
# do not touch any existing thing, it may be precious
93+
if not op.lexists(events_file):
94+
lgr.debug("Generating %s", events_file)
95+
with open(events_file, 'w') as f:
96+
f.write("onset\tduration\ttrial_type\tresponse_time\tstim_file\tTODO -- fill in rows and add more tab-separated columns if desired")
9597
# extract tasks files stubs
9698
for task_acq, fields in tasks.items():
9799
task_file = op.join(path, task_acq + '_bold.json')
98-
lgr.debug("Generating %s", task_file)
99-
fields["TaskName"] = ("TODO: full task name for %s" %
100-
task_acq.split('_')[0].split('-')[1])
101-
fields["CogAtlasID"] = "TODO"
102-
with open(task_file, 'w') as f:
103-
f.write(json_dumps_pretty(fields, indent=2, sort_keys=True))
100+
# do not touch any existing thing, it may be precious
101+
if not op.lexists(task_file):
102+
lgr.debug("Generating %s", task_file)
103+
fields["TaskName"] = ("TODO: full task name for %s" %
104+
task_acq.split('_')[0].split('-')[1])
105+
fields["CogAtlasID"] = "TODO"
106+
with open(task_file, 'w') as f:
107+
f.write(json_dumps_pretty(fields, indent=2, sort_keys=True))
104108

105109

106110
def tuneup_bids_json_files(json_files):
@@ -133,8 +137,9 @@ def tuneup_bids_json_files(json_files):
133137
json_basename = '_'.join(jsonfile.split('_')[:-1])
134138
# if we got by now all needed .json files -- we can fix them up
135139
# unfortunately order of "items" is not guaranteed atm
136-
if len(glob(json_basename + '*.json')) == 3:
137-
json_phasediffname = json_basename + '_phasediff.json'
140+
json_phasediffname = json_basename + '_phasediff.json'
141+
json_mag = json_basename + '_magnitude*.json'
142+
if op.exists(json_phasediffname) and len(glob(json_mag)) >= 1:
138143
json_ = load_json(json_phasediffname)
139144
# TODO: we might want to reorder them since ATM
140145
# the one for shorter TE is the 2nd one!
@@ -293,10 +298,14 @@ def get_formatted_scans_key_row(item):
293298
force=True))
294299
# we need to store filenames and acquisition times
295300
# parse date and time and get it into isoformat
296-
date = mw.dcm_data.ContentDate
297-
time = mw.dcm_data.ContentTime.split('.')[0]
298-
td = time + date
299-
acq_time = datetime.strptime(td, '%H%M%S%Y%m%d').isoformat()
301+
try:
302+
date = mw.dcm_data.ContentDate
303+
time = mw.dcm_data.ContentTime.split('.')[0]
304+
td = time + date
305+
acq_time = datetime.strptime(td, '%H%M%S%Y%m%d').isoformat()
306+
except AttributeError as exc:
307+
lgr.warning("Failed to get date/time for the content: %s", str(exc))
308+
acq_time = None
300309
# add random string
301310
randstr = ''.join(map(chr, sample(k=8, population=range(33, 127))))
302311
try:
@@ -326,6 +335,10 @@ def convert_sid_bids(subject_id):
326335
"""
327336
cleaner = lambda y: ''.join([x for x in y if x.isalnum()])
328337
sid = cleaner(subject_id)
338+
if not sid:
339+
raise ValueError(
340+
"Subject ID became empty after cleanup. Please provide manually "
341+
"a suitable alphanumeric subject ID")
329342
lgr.warning('{0} contained nonalphanumeric character(s), subject '
330343
'ID was cleaned to be {1}'.format(subject_id, sid))
331344
return sid, subject_id

heudiconv/cli/run.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ def process_extra_commands(outdir, args):
9999
def main(argv=None):
100100
parser = get_parser()
101101
args = parser.parse_args(argv)
102+
# exit if nothing to be done
103+
if not args.files and not args.dicom_dir_template and not args.command:
104+
lgr.warning("Nothing to be done - displaying usage help")
105+
parser.print_help()
106+
sys.exit(1)
102107
# To be done asap so anything random is deterministic
103108
if args.random_seed is not None:
104109
import random
@@ -237,6 +242,9 @@ def process_args(args):
237242
#
238243
# Load heuristic -- better do it asap to make sure it loads correctly
239244
#
245+
if not args.heuristic:
246+
raise RuntimeError("No heuristic specified - add to arguments and rerun")
247+
240248
heuristic = load_heuristic(args.heuristic)
241249

242250
study_sessions = get_study_sessions(args.dicom_dir_template, args.files,

0 commit comments

Comments
 (0)