Skip to content

Commit ec53e59

Browse files
authored
Merge pull request #451 from tsalo/enh/retain-subsecond
[ENH] Retain sub-second resolution in scans files
2 parents 1f6ae74 + c5fe64f commit ec53e59

File tree

5 files changed

+61
-20
lines changed

5 files changed

+61
-20
lines changed

heudiconv/bids.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,24 @@
2121
json_dumps_pretty,
2222
set_readonly,
2323
is_readonly,
24+
get_datetime,
2425
)
2526

2627
lgr = logging.getLogger(__name__)
2728

29+
# Fields to be populated in _scans files. Order matters
30+
SCANS_FILE_FIELDS = OrderedDict([
31+
("filename", OrderedDict([
32+
("Description", "Name of the nifti file")])),
33+
("acq_time", OrderedDict([
34+
("LongName", "Acquisition time"),
35+
("Description", "Acquisition time of the particular scan")])),
36+
("operator", OrderedDict([
37+
("Description", "Name of the operator")])),
38+
("randstr", OrderedDict([
39+
("LongName", "Random string"),
40+
("Description", "md5 hash of UIDs")])),
41+
])
2842

2943
class BIDSError(Exception):
3044
pass
@@ -359,22 +373,9 @@ def add_rows_to_scans_keys_file(fn, newrows):
359373
# _scans.tsv). This auto generation will make BIDS-validator happy.
360374
scans_json = '.'.join(fn.split('.')[:-1] + ['json'])
361375
if not op.lexists(scans_json):
362-
save_json(scans_json,
363-
OrderedDict([
364-
("filename", OrderedDict([
365-
("Description", "Name of the nifti file")])),
366-
("acq_time", OrderedDict([
367-
("LongName", "Acquisition time"),
368-
("Description", "Acquisition time of the particular scan")])),
369-
("operator", OrderedDict([
370-
("Description", "Name of the operator")])),
371-
("randstr", OrderedDict([
372-
("LongName", "Random string"),
373-
("Description", "md5 hash of UIDs")])),
374-
]),
375-
sort_keys=False)
376+
save_json(scans_json, SCANS_FILE_FIELDS, sort_keys=False)
376377

377-
header = ['filename', 'acq_time', 'operator', 'randstr']
378+
header = SCANS_FILE_FIELDS
378379
# prepare all the data rows
379380
data_rows = [[k] + v for k, v in fnames2info.items()]
380381
# sort by the date/filename
@@ -406,9 +407,8 @@ def get_formatted_scans_key_row(dcm_fn):
406407
# parse date and time and get it into isoformat
407408
try:
408409
date = dcm_data.ContentDate
409-
time = dcm_data.ContentTime.split('.')[0]
410-
td = time + date
411-
acq_time = datetime.strptime(td, '%H%M%S%Y%m%d').isoformat()
410+
time = dcm_data.ContentTime
411+
acq_time = get_datetime(date, time)
412412
except (AttributeError, ValueError) as exc:
413413
lgr.warning("Failed to get date/time for the content: %s", str(exc))
414414
acq_time = ''

heudiconv/tests/test_heuristics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def test_scans_keys_reproin(tmpdir, invocation):
116116
if i != 0:
117117
assert(os.path.exists(pjoin(dirname(scans_keys[0]), row[0])))
118118
assert(re.match(
119-
'^[\d]{4}-[\d]{2}-[\d]{2}T[\d]{2}:[\d]{2}:[\d]{2}$',
119+
'^[\d]{4}-[\d]{2}-[\d]{2}T[\d]{2}:[\d]{2}:[\d]{2}.[\d]{6}$',
120120
row[1]))
121121

122122

heudiconv/tests/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def test_get_formatted_scans_key_row():
173173

174174
row1 = get_formatted_scans_key_row(dcm_fn)
175175
assert len(row1) == 3
176-
assert row1[0] == '2016-10-14T09:26:36'
176+
assert row1[0] == '2016-10-14T09:26:36.693000'
177177
assert row1[1] == 'n/a'
178178
prandstr1 = row1[2]
179179

heudiconv/tests/test_utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
load_json,
1111
create_tree,
1212
save_json,
13+
get_datetime,
1314
JSONDecodeError)
1415

1516
import pytest
@@ -85,3 +86,12 @@ def test_load_json(tmpdir, caplog):
8586
save_json(valid_json_file, vcontent)
8687

8788
assert load_json(valid_json_file) == vcontent
89+
90+
91+
def test_get_datetime():
92+
"""
93+
Test utils.get_datetime()
94+
"""
95+
assert get_datetime('20200512', '162130') == '2020-05-12T16:21:30'
96+
assert get_datetime('20200512', '162130.5') == '2020-05-12T16:21:30.500000'
97+
assert get_datetime('20200512', '162130.5', microseconds=False) == '2020-05-12T16:21:30'

heudiconv/utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from collections import namedtuple
1414
from glob import glob
1515
from subprocess import check_output
16+
from datetime import datetime
1617

1718
from nipype.utils.filemanip import which
1819

@@ -505,3 +506,33 @@ def get_typed_attr(obj, attr, _type, default=None):
505506
except (TypeError, ValueError):
506507
return default
507508
return val
509+
510+
511+
def get_datetime(date, time, *, microseconds=True):
512+
"""
513+
Combine date and time from dicom to isoformat.
514+
515+
Parameters
516+
----------
517+
date : str
518+
Date in YYYYMMDD format.
519+
time : str
520+
Time in either HHMMSS.ffffff format or HHMMSS format.
521+
microseconds: bool, optional
522+
Either to include microseconds in the output
523+
524+
Returns
525+
-------
526+
datetime_str : str
527+
Combined date and time in ISO format, with microseconds as
528+
if fraction was provided in 'time', and 'microseconds' was
529+
True.
530+
"""
531+
if '.' not in time:
532+
# add dummy microseconds if not available for strptime to parse
533+
time += '.000000'
534+
td = time + ':' + date
535+
datetime_str = datetime.strptime(td, '%H%M%S.%f:%Y%m%d').isoformat()
536+
if not microseconds:
537+
datetime_str = datetime_str.split('.', 1)[0]
538+
return datetime_str

0 commit comments

Comments
 (0)