Skip to content

Commit 9fb1ae6

Browse files
author
DBIC BIDS Team
committed
Merge branch 'master' of git://github.com/nipy/heudiconv
Conflicts: heudiconv/heuristics/reproin.py - retained committed part but removed deprecated variable
2 parents ad2bd36 + 2ff09b1 commit 9fb1ae6

25 files changed

+545
-188
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: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,70 @@ 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] - TBD
8-
First release after major refactor
7+
## [0.5.1] - 2018-07-05
8+
Bugfix release
9+
910
### Added
10-
- [CHANGELOG.md](https://github.com/nipy/heudiconv/blob/master/CHANGELOG.md)
11-
- [Regression testing](https://github.com/nipy/heudiconv/blob/master/tests/test_regression.py) on real data (using datalad)
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+
34+
## [0.5] - 2018-03-01
35+
The first release after major refactoring:
36+
1237
### Changed
1338
- Refactored into a proper `heudiconv` Python module
39+
- `heuristics` is now a `heudiconv.heuristics` submodule
40+
- you can specify shipped heuristics by name (e.g. `-f reproin`)
41+
without providing full path to their files
42+
- you need to use `--files` (not just positional argument(s)) if not
43+
using `--dicom_dir_templates` or `--subjects` to point to data files
44+
or directories with input DICOMs
1445
- `Dockerfile` is generated by [neurodocker](https://github.com/kaczmarj/neurodocker)
1546
- Logging verbosity reduced
1647
- Increased leniency with missing DICOM fields
17-
- dbic_bids heuristic renamed into reproin
48+
- `dbic_bids` heuristic renamed into reproin
49+
### Added
50+
- [LICENSE](https://github.com/nipy/heudiconv/blob/master/LICENSE)
51+
with Apache 2.0 license for the project
52+
- [CHANGELOG.md](https://github.com/nipy/heudiconv/blob/master/CHANGELOG.md)
53+
- [Regression testing](https://github.com/nipy/heudiconv/blob/master/tests/test_regression.py) on real data (using datalad)
54+
- A dedicated [ReproIn](https://github.com/repronim/reproin) project
55+
with details about ReproIn setup/specification and operation using
56+
`reproin` heuristic shipped with heudiconv
57+
- [utils/test-compare-two-versions.sh](utils/test-compare-two-versions.sh)
58+
helper to compare conversions with two different versions of heudiconv
1859
### Removed
19-
### Deprecated
60+
- Support for converters other than `dcm2niix`, which is now the default.
61+
Explicitly specify `-c none` to only prepare conversion specification
62+
files without performing actual conversion
2063
### Fixed
64+
- Compatibility with Nipype 1.0, PyDicom 1.0, and upcoming DataLad 0.10
2165
- Consistency with converted files permissions
2266
- Ensured subject id for BIDS conversions will be BIDS compliant
2367
- Re-add `seqinfo` fields as column names in generated `dicominfo`
24-
### Security
68+
- More robust sanity check of the regex reformatted .json file to avoid
69+
numeric precision issues
70+
- Many other various issues
2571

2672
## [0.4] - 2017-10-15
2773
A usable release to support [DBIC] use-case

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: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
from random import sample
1111
from glob import glob
1212

13-
import dicom as dcm
14-
import dcmstack as ds
13+
from heudiconv.external.pydicom import dcm
1514

1615
from .parser import find_files
1716
from .utils import (
@@ -30,7 +29,7 @@ def populate_bids_templates(path, defaults={}):
3029

3130
lgr.info("Populating template files under %s", path)
3231
descriptor = op.join(path, 'dataset_description.json')
33-
if not op.exists(descriptor):
32+
if not op.lexists(descriptor):
3433
save_json(descriptor,
3534
OrderedDict([
3635
('Name', "TODO: name of the dataset"),
@@ -90,18 +89,22 @@ def populate_bids_templates(path, defaults={}):
9089
suf = '_bold.json'
9190
assert fpath.endswith(suf)
9291
events_file = fpath[:-len(suf)] + '_events.tsv'
93-
lgr.debug("Generating %s", events_file)
94-
with open(events_file, 'w') as f:
95-
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")
9697
# extract tasks files stubs
9798
for task_acq, fields in tasks.items():
9899
task_file = op.join(path, task_acq + '_bold.json')
99-
lgr.debug("Generating %s", task_file)
100-
fields["TaskName"] = ("TODO: full task name for %s" %
101-
task_acq.split('_')[0].split('-')[1])
102-
fields["CogAtlasID"] = "TODO"
103-
with open(task_file, 'w') as f:
104-
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))
105108

106109

107110
def tuneup_bids_json_files(json_files):
@@ -134,8 +137,9 @@ def tuneup_bids_json_files(json_files):
134137
json_basename = '_'.join(jsonfile.split('_')[:-1])
135138
# if we got by now all needed .json files -- we can fix them up
136139
# unfortunately order of "items" is not guaranteed atm
137-
if len(glob(json_basename + '*.json')) == 3:
138-
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:
139143
json_ = load_json(json_phasediffname)
140144
# TODO: we might want to reorder them since ATM
141145
# the one for shorter TE is the 2nd one!
@@ -288,15 +292,20 @@ def get_formatted_scans_key_row(item):
288292
289293
"""
290294
dcm_fn = item[-1][0]
295+
from heudiconv.external.dcmstack import ds
291296
mw = ds.wrapper_from_data(dcm.read_file(dcm_fn,
292297
stop_before_pixels=True,
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

0 commit comments

Comments
 (0)