Skip to content

Commit 0e8860f

Browse files
author
DBIC BIDS Team
committed
Merge remote-tracking branch 'origin/bf-chmod' into enh-dbic2
2 parents 3e16e69 + e4fb4ee commit 0e8860f

File tree

5 files changed

+59
-35
lines changed

5 files changed

+59
-35
lines changed

Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ RUN conda install -y -c conda-forge nipype && \
1515
pip install https://github.com/moloney/dcmstack/archive/c12d27d2c802d75a33ad70110124500a83e851ee.zip && \
1616
pip install datalad && \
1717
conda clean -tipsy && rm -rf ~/.pip/
18-
RUN cd /tmp && git clone https://github.com/neurolabusc/dcm2niix.git && \
18+
RUN apt-get update && apt-get upgrade -y && \
19+
apt-get install -y pigz && \
20+
apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y && \
21+
cd /tmp && git clone https://github.com/neurolabusc/dcm2niix.git && \
1922
cd dcm2niix && \
20-
git checkout 60bab318ee738b644ebb1396bbb8cbe1b006218f && \
23+
git checkout 6ba27b9befcbae925209664bb8acbb00e266114a && \
2124
mkdir build && cd build && cmake -DBATCH_VERSION=ON .. && \
2225
make && make install && \
2326
cd / && rm -rf /tmp/dcm2niix

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ install:
66
mkdir -p $(DESTDIR)$(PREFIX)/share/heudiconv/heuristics
77
mkdir -p $(DESTDIR)$(PREFIX)/share/doc/heudiconv/examples/heuristics
88
mkdir -p $(DESTDIR)$(PREFIX)/bin
9-
install -t $(DESTDIR)$(PREFIX)/bin bin/heudiconv
10-
install -m 644 -t $(DESTDIR)$(PREFIX)/share/heudiconv/heuristics heuristics/*
9+
install bin/heudiconv $(DESTDIR)$(PREFIX)/bin
10+
install -m 644 heuristics/* $(DESTDIR)$(PREFIX)/share/heudiconv/heuristics
1111

1212
uninstall:
1313
rm -f $(DESTDIR)$(PREFIX)/bin/heudiconv

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,17 @@ as long as the following dependencies are in your path you can use the script
3333
- nibabel
3434
- dcm2niix
3535

36-
## Example conversion using Docker
36+
## Tutorial with example conversion to BIDS format using Docker
37+
Please read this tutorial to understand how heudiconv works in practice.
38+
3739
[Slides here](http://nipy.org/workshops/2017-03-boston/lectures/bids-heudiconv/#1)
3840

41+
To generate lean BIDS output, consider using both the `-b` and the `--minmeta` flags
42+
to your heudiconv command. The `-b` flag generates a json file with BIDS keys, while
43+
the `--minmeta` flag restricts the json file to only BIDS keys. Without `--minmeta`,
44+
the json file and the associated Nifti file contains DICOM metadata extracted using
45+
dicomstack.
46+
3947
## How it works (in some more detail)
4048

4149
Call `heudiconv` like this:

bin/heudiconv

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ It has multiple modes of operation
2020
DICOMs are sorted based on study UID, and layed out using specified heuristic
2121
"""
2222

23-
__version__ = '0.3'
23+
__version__ = '0.4'
2424

2525
import argparse
2626
from glob import glob
@@ -245,7 +245,8 @@ def json_dumps_pretty(j, indent=2, sort_keys=True):
245245
# no spaces after [
246246
js_ = re.sub('\[ ', '[', js_)
247247
j_ = json.loads(js_)
248-
assert(j == j_)
248+
# Removed assert as it does not do any floating point comparison
249+
#assert(j == j_)
249250
return js_
250251

251252

@@ -313,6 +314,14 @@ def find_files(regex, topdir=curdir, exclude=None, exclude_vcs=True, dirs=False)
313314
find_files.__doc__ %= (_VCS_REGEX,)
314315

315316

317+
def is_readonly(path):
318+
"""Return True if it is a fully read-only file (dereferences the symlink)
319+
"""
320+
# get current permissions
321+
perms = stat.S_IMODE(os.lstat(os.path.realpath(path)).st_mode)
322+
return not bool(perms & ALL_CAN_WRITE) # should be true if anyone is allowed to write
323+
324+
316325
def set_readonly(path, read_only=True):
317326
"""Make file read only or writeable while preserving "access levels"
318327
@@ -453,11 +462,9 @@ def group_dicoms_into_seqinfos(
453462
series_id = series_id + (file_studyUID,)
454463

455464

456-
#print fidx, N, filename
457465
ingrp = False
458466
for idx in range(len(mwgroup)):
459467
same = mw.is_same_series(mwgroup[idx])
460-
#print idx, same, groups[idx][0]
461468
if same:
462469
# the same series should have the same study uuid
463470
assert mwgroup[idx].dcm_data.get('StudyInstanceUID', None) == file_studyUID
@@ -792,7 +799,7 @@ def get_dicom_series_time(dicom_list):
792799
import calendar
793800
import dicom as dcm
794801

795-
dcm = dcm.read_file(dicom_list[0], stop_before_pixels=True)
802+
dcm = dcm.read_file(dicom_list[0], stop_before_pixels=True, force=True)
796803
dcm_date = dcm.SeriesDate # YYYYMMDD
797804
dcm_time = dcm.SeriesTime # HHMMSS.MICROSEC
798805
dicom_time_str = dcm_date + dcm_time.split('.', 1)[0] # YYYYMMDDHHMMSS
@@ -1017,7 +1024,7 @@ def get_formatted_scans_key_row(item):
10171024
10181025
"""
10191026
dcm_fn = item[-1][0]
1020-
mw = ds.wrapper_from_data(dcm.read_file(dcm_fn, stop_before_pixels=True))
1027+
mw = ds.wrapper_from_data(dcm.read_file(dcm_fn, stop_before_pixels=True, force=True))
10211028
# we need to store filenames and acquisition times
10221029
# parse date and time and get it into isoformat
10231030
date = mw.dcm_data.ContentDate
@@ -1026,7 +1033,12 @@ def get_formatted_scans_key_row(item):
10261033
acq_time = datetime.strptime(td, '%H%M%S%Y%m%d').isoformat()
10271034
# add random string
10281035
randstr = ''.join(map(chr, sample(k=8, population=range(33, 127))))
1029-
row = [acq_time, mw.dcm_data.PerformingPhysicianName, randstr]
1036+
# Catch AttributeError if PerformingPhysicianName info is missing
1037+
try:
1038+
perfphys = mw.dcm_data.PerformingPhysicianName
1039+
except AttributeError:
1040+
perfphys = 'n/a'
1041+
row = [acq_time, perfphys, randstr]
10301042
# empty entries should be 'n/a'
10311043
# https://github.com/dartmouth-pbs/heudiconv/issues/32
10321044
row = ['n/a' if not str(e) else e for e in row]
@@ -1075,8 +1087,9 @@ def _find_subj_ses(f_name):
10751087
# we will allow the match at either directories or within filename
10761088
# assuming that bids layout is "correct"
10771089
regex = re.compile('sub-(?P<subj>[a-zA-Z0-9]*)([/_]ses-(?P<ses>[a-zA-Z0-9]*))?')
1078-
res = regex.search(f_name).groupdict()
1079-
return res.get('subj'), res.get('ses', None)
1090+
regex_res = regex.search(f_name)
1091+
res = regex_res.groupdict() if regex_res else {}
1092+
return res.get('subj', None), res.get('ses', None)
10801093

10811094

10821095
def save_scans_key(item, bids_files):
@@ -1103,6 +1116,13 @@ def save_scans_key(item, bids_files):
11031116
f_name = f_name.replace('json', 'nii.gz')
11041117
rows[f_name] = get_formatted_scans_key_row(item)
11051118
subj_, ses_ = _find_subj_ses(f_name)
1119+
if not subj_:
1120+
lgr.warning(
1121+
"Failed to detect fullfilled BIDS layout. "
1122+
"No scans.tsv file(s) will be produced for %s",
1123+
", ".join(bids_files)
1124+
)
1125+
return
11061126
if subj and subj_ != subj:
11071127
raise ValueError(
11081128
"We found before subject %s but now deduced %s from %s"
@@ -1167,10 +1187,14 @@ def tuneup_bids_json_files(json_files):
11671187
except IOError as exc:
11681188
lgr.error("Failed to open magnitude file: %s", exc)
11691189

1170-
# might have been made R/O already
1171-
set_readonly(json_phasediffname, False)
1190+
# might have been made R/O already, but if not -- it will be set
1191+
# only later in the pipeline, so we must not make it read-only yet
1192+
was_readonly = is_readonly(json_phasediffname)
1193+
if was_readonly:
1194+
set_readonly(json_phasediffname, False)
11721195
json.dump(json_, open(json_phasediffname, 'w'), indent=2)
1173-
set_readonly(json_phasediffname)
1196+
if was_readonly:
1197+
set_readonly(json_phasediffname)
11741198

11751199
# phasediff one should contain two PhaseDiff's
11761200
# -- one for original amplitude and the other already replicating what is there
@@ -1225,23 +1249,6 @@ def embed_metadata_from_dicoms(converter, is_bids, item_dicoms, outname,
12251249
cwd = os.getcwd()
12261250
lgr.debug("Embedding into %s based on dicoms[0]=%s for nifti %s", scaninfo, item_dicoms[0], outname)
12271251
try:
1228-
"""
1229-
Ran into
1230-
INFO: Executing node embedder in dir: /tmp/heudiconvdcm2W3UQ7/embedder
1231-
ERROR: Embedding failed: [Errno 13] Permission denied: '/inbox/BIDS/tmp/test2-jessie/Wheatley/Beau/1007_personality/sub-sid000138/fmap/sub-sid000138_3mm_run-01_phasediff.json'
1232-
while
1233-
HEUDICONV_LOGLEVEL=WARNING time bin/heudiconv -f heuristics/dbic_bids.py -c dcm2niix -o /inbox/BIDS/tmp/test2-jessie --bids --datalad /inbox/DICOM/2017/01/28/A000203
1234-
1235-
so it seems that there is a filename collision so it tries to save into the same file name
1236-
and there was a screw up for that A
1237-
1238-
/mnt/btrfs/dbic/inbox/DICOM/2017/01/28/A000203
1239-
StudySessionInfo(locator='Wheatley/Beau/1007_personality', session=None, subject='sid000138') 16 sequences
1240-
StudySessionInfo(locator='Wheatley/Beau/1007_personality', session=None, subject='a000203') 2 sequences
1241-
1242-
1243-
in that one though
1244-
"""
12451252
if global_options['overwrite'] and os.path.lexists(scaninfo):
12461253
# TODO: handle annexed file case
12471254
if not os.path.islink(scaninfo):
@@ -1392,7 +1399,8 @@ def convert_dicoms(sid,
13921399
with_prov=with_prov,
13931400
is_bids=is_bids,
13941401
sourcedir=sourcedir,
1395-
outdir=tdir)
1402+
outdir=tdir,
1403+
min_meta=min_meta)
13961404

13971405
if is_bids:
13981406
if seqinfo:

tests/test_main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,18 @@ def test_make_readonly(tmpdir):
205205
pathname = str(path)
206206
with open(pathname, 'w'):
207207
pass
208+
symname = pathname + 'link'
209+
os.symlink(pathname, symname)
208210
for orig, ro, rw in [
209211
(0o600, 0o400, 0o600), # fully returned
210212
(0o624, 0o404, 0o606), # it will not get write bit where it is not readable
211213
(0o1777, 0o1555, 0o1777), # and other bits should be preserved
212214
]:
213215
os.chmod(pathname, orig)
216+
assert not heudiconv.is_readonly(pathname)
214217
assert heudiconv.set_readonly(pathname) == ro
218+
assert heudiconv.is_readonly(pathname)
215219
assert stat.S_IMODE(os.lstat(pathname).st_mode) == ro
216220
# and it should go back if we set it back to non-read_only
217221
assert heudiconv.set_readonly(pathname, read_only=False) == rw
222+
assert not heudiconv.is_readonly(pathname)

0 commit comments

Comments
 (0)