Skip to content

Commit

Permalink
Merge pull request #451 from tsalo/enh/retain-subsecond
Browse files Browse the repository at this point in the history
[ENH] Retain sub-second resolution in scans files
  • Loading branch information
yarikoptic authored May 28, 2020
2 parents 1f6ae74 + c5fe64f commit ec53e59
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 20 deletions.
36 changes: 18 additions & 18 deletions heudiconv/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,24 @@
json_dumps_pretty,
set_readonly,
is_readonly,
get_datetime,
)

lgr = logging.getLogger(__name__)

# Fields to be populated in _scans files. Order matters
SCANS_FILE_FIELDS = OrderedDict([
("filename", OrderedDict([
("Description", "Name of the nifti file")])),
("acq_time", OrderedDict([
("LongName", "Acquisition time"),
("Description", "Acquisition time of the particular scan")])),
("operator", OrderedDict([
("Description", "Name of the operator")])),
("randstr", OrderedDict([
("LongName", "Random string"),
("Description", "md5 hash of UIDs")])),
])

class BIDSError(Exception):
pass
Expand Down Expand Up @@ -359,22 +373,9 @@ def add_rows_to_scans_keys_file(fn, newrows):
# _scans.tsv). This auto generation will make BIDS-validator happy.
scans_json = '.'.join(fn.split('.')[:-1] + ['json'])
if not op.lexists(scans_json):
save_json(scans_json,
OrderedDict([
("filename", OrderedDict([
("Description", "Name of the nifti file")])),
("acq_time", OrderedDict([
("LongName", "Acquisition time"),
("Description", "Acquisition time of the particular scan")])),
("operator", OrderedDict([
("Description", "Name of the operator")])),
("randstr", OrderedDict([
("LongName", "Random string"),
("Description", "md5 hash of UIDs")])),
]),
sort_keys=False)
save_json(scans_json, SCANS_FILE_FIELDS, sort_keys=False)

header = ['filename', 'acq_time', 'operator', 'randstr']
header = SCANS_FILE_FIELDS
# prepare all the data rows
data_rows = [[k] + v for k, v in fnames2info.items()]
# sort by the date/filename
Expand Down Expand Up @@ -406,9 +407,8 @@ def get_formatted_scans_key_row(dcm_fn):
# parse date and time and get it into isoformat
try:
date = dcm_data.ContentDate
time = dcm_data.ContentTime.split('.')[0]
td = time + date
acq_time = datetime.strptime(td, '%H%M%S%Y%m%d').isoformat()
time = dcm_data.ContentTime
acq_time = get_datetime(date, time)
except (AttributeError, ValueError) as exc:
lgr.warning("Failed to get date/time for the content: %s", str(exc))
acq_time = ''
Expand Down
2 changes: 1 addition & 1 deletion heudiconv/tests/test_heuristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_scans_keys_reproin(tmpdir, invocation):
if i != 0:
assert(os.path.exists(pjoin(dirname(scans_keys[0]), row[0])))
assert(re.match(
'^[\d]{4}-[\d]{2}-[\d]{2}T[\d]{2}:[\d]{2}:[\d]{2}$',
'^[\d]{4}-[\d]{2}-[\d]{2}T[\d]{2}:[\d]{2}:[\d]{2}.[\d]{6}$',
row[1]))


Expand Down
2 changes: 1 addition & 1 deletion heudiconv/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def test_get_formatted_scans_key_row():

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

Expand Down
10 changes: 10 additions & 0 deletions heudiconv/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
load_json,
create_tree,
save_json,
get_datetime,
JSONDecodeError)

import pytest
Expand Down Expand Up @@ -85,3 +86,12 @@ def test_load_json(tmpdir, caplog):
save_json(valid_json_file, vcontent)

assert load_json(valid_json_file) == vcontent


def test_get_datetime():
"""
Test utils.get_datetime()
"""
assert get_datetime('20200512', '162130') == '2020-05-12T16:21:30'
assert get_datetime('20200512', '162130.5') == '2020-05-12T16:21:30.500000'
assert get_datetime('20200512', '162130.5', microseconds=False) == '2020-05-12T16:21:30'
31 changes: 31 additions & 0 deletions heudiconv/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from collections import namedtuple
from glob import glob
from subprocess import check_output
from datetime import datetime

from nipype.utils.filemanip import which

Expand Down Expand Up @@ -505,3 +506,33 @@ def get_typed_attr(obj, attr, _type, default=None):
except (TypeError, ValueError):
return default
return val


def get_datetime(date, time, *, microseconds=True):
"""
Combine date and time from dicom to isoformat.
Parameters
----------
date : str
Date in YYYYMMDD format.
time : str
Time in either HHMMSS.ffffff format or HHMMSS format.
microseconds: bool, optional
Either to include microseconds in the output
Returns
-------
datetime_str : str
Combined date and time in ISO format, with microseconds as
if fraction was provided in 'time', and 'microseconds' was
True.
"""
if '.' not in time:
# add dummy microseconds if not available for strptime to parse
time += '.000000'
td = time + ':' + date
datetime_str = datetime.strptime(td, '%H%M%S.%f:%Y%m%d').isoformat()
if not microseconds:
datetime_str = datetime_str.split('.', 1)[0]
return datetime_str

0 comments on commit ec53e59

Please sign in to comment.