From 67544f26495c01db2e6123c9f252b8216bc10139 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Mon, 5 Apr 2021 14:26:51 -0500 Subject: [PATCH 01/14] Round instead of ceil --- bids/variables/variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/variables.py b/bids/variables/variables.py index af3ec0598..adb295b9e 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -504,7 +504,7 @@ def _build_entity_index(self, run_info, sampling_rate): interval = int(round(1000. / sampling_rate)) _timestamps = [] for run in run_info: - reps = int(math.ceil(run.duration * sampling_rate)) + reps = int(np.round(run.duration * sampling_rate)) ent_vals = list(run.entities.values()) df = pd.DataFrame([ent_vals] * reps, columns=list(run.entities.keys())) ts = pd.date_range(0, periods=len(df), freq='%sms' % interval) From 6c8324f976e0901770ab682676973ac8c8d135a2 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Tue, 6 Apr 2021 18:38:35 -0500 Subject: [PATCH 02/14] Add n_vols to RunInfo and use for reps value in _build_entitiy_index --- bids/analysis/tests/test_transformations.py | 4 +- bids/variables/collections.py | 14 +------ bids/variables/entities.py | 9 +++-- bids/variables/io.py | 4 +- bids/variables/tests/test_collections.py | 2 +- bids/variables/tests/test_entities.py | 4 +- bids/variables/tests/test_variables.py | 2 +- bids/variables/variables.py | 45 +++++++++++++++++++-- 8 files changed, 57 insertions(+), 27 deletions(-) diff --git a/bids/analysis/tests/test_transformations.py b/bids/analysis/tests/test_transformations.py index 93aa29699..283f6659d 100644 --- a/bids/analysis/tests/test_transformations.py +++ b/bids/analysis/tests/test_transformations.py @@ -50,7 +50,7 @@ def sparse_run_variable_with_missing_values(): 'duration': [1.2, 1.6, 0.8, 2], 'amplitude': [1, 1, np.nan, 1] }) - run_info = [RunInfo({'subject': '01'}, 20, 2, 'dummy.nii.gz')] + run_info = [RunInfo({'subject': '01'}, 20, 2, 'dummy.nii.gz', 10)] var = SparseRunVariable( name='var', data=data, run_info=run_info, source='events') return BIDSRunVariableCollection([var]) @@ -114,7 +114,7 @@ def test_convolve_impulse(): 'duration': [0, 0], 'amplitude': [1, 1] }) - run_info = [RunInfo({'subject': '01'}, 20, 2, 'dummy.nii.gz')] + run_info = [RunInfo({'subject': '01'}, 20, 2, 'dummy.nii.gz', 10)] var = SparseRunVariable( name='var', data=data, run_info=run_info, source='events') coll = BIDSRunVariableCollection([var]) diff --git a/bids/variables/collections.py b/bids/variables/collections.py index 81dc0e32d..00cac9a22 100644 --- a/bids/variables/collections.py +++ b/bids/variables/collections.py @@ -324,21 +324,9 @@ def _get_sampling_rate(self, sampling_rate): if sampling_rate is None: return self.sampling_rate - if isinstance(sampling_rate, (float, int)): + if isinstance(sampling_rate, (float, int)) or sampling_rate == 'TR': return sampling_rate - if sampling_rate == 'TR': - trs = {var.run_info[0].tr for var in self.variables.values()} - if not trs: - raise ValueError("Repetition time unavailable; specify " - "sampling_rate in Hz explicitly or set to" - " 'highest'.") - elif len(trs) > 1: - raise ValueError("Non-unique Repetition times found " - "({!r}); specify sampling_rate explicitly" - .format(trs)) - return 1. / trs.pop() - if sampling_rate.lower() == 'highest': dense_vars = self.get_dense_variables() # If no dense variables are available, fall back on instance SR diff --git a/bids/variables/entities.py b/bids/variables/entities.py index b3d77499b..19f11c1ae 100644 --- a/bids/variables/entities.py +++ b/bids/variables/entities.py @@ -55,10 +55,11 @@ class RunNode(Node): The task name for this run. """ - def __init__(self, entities, image_file, duration, repetition_time): + def __init__(self, entities, image_file, duration, repetition_time, n_vols): self.image_file = image_file self.duration = duration self.repetition_time = repetition_time + self.n_vols = n_vols super(RunNode, self).__init__('run', entities) def get_info(self): @@ -68,18 +69,18 @@ def get_info(self): # a RunInfo or any containing object. entities = dict(self.entities) return RunInfo(entities, self.duration, - self.repetition_time, self.image_file) + self.repetition_time, self.image_file, self.n_vols) # Stores key information for each Run. -RunInfo_ = namedtuple('RunInfo', ['entities', 'duration', 'tr', 'image']) +RunInfo_ = namedtuple('RunInfo', ['entities', 'duration', 'tr', 'image', 'n_vols']) # Wrap with class to provide docstring class RunInfo(RunInfo_): """ A namedtuple storing run-related information. - Properties include 'entities', 'duration', 'tr', and 'image'. + Properties include 'entities', 'duration', 'tr', and 'image', 'n_vols'. """ pass diff --git a/bids/variables/io.py b/bids/variables/io.py index 04b452fab..c91400bbd 100644 --- a/bids/variables/io.py +++ b/bids/variables/io.py @@ -211,6 +211,7 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None, "as a fallback. Please check that the image files are " "available, or manually specify the scan duration.") raise ValueError(msg) from e + nvols = None # We don't want to pass all the image file's entities onto get_node(), # as there can be unhashable nested slice timing values, and this also @@ -247,7 +248,8 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None, } run = dataset.create_node('run', entities, image_file=img_f, - duration=duration, repetition_time=tr) + duration=duration, repetition_time=trm + n_vols=nvols) run_info = run.get_info() # Process event files diff --git a/bids/variables/tests/test_collections.py b/bids/variables/tests/test_collections.py index b17884fa8..7c68c6eb5 100644 --- a/bids/variables/tests/test_collections.py +++ b/bids/variables/tests/test_collections.py @@ -55,7 +55,7 @@ def test_run_variable_collection_get_sampling_rate(run_coll): coll = run_coll.clone() assert coll._get_sampling_rate(None) == 10 assert coll._get_sampling_rate('TR') == 0.5 - coll.variables['RT'].run_info[0] = RunInfo({}, 200, 10, None) + coll.variables['RT'].run_info[0] = RunInfo({}, 200, 10, None, 20) with pytest.raises(ValueError) as exc: coll._get_sampling_rate('TR') assert str(exc.value).startswith('Non-unique') diff --git a/bids/variables/tests/test_entities.py b/bids/variables/tests/test_entities.py index beaf1ab84..6dc0f59da 100644 --- a/bids/variables/tests/test_entities.py +++ b/bids/variables/tests/test_entities.py @@ -44,9 +44,11 @@ def test_get_or_create_node(layout1): run = index.get_or_create_node('run', img.entities, image_file=img.filename, duration=480, - repetition_time=2) + repetition_time=2, + n_vols=480/2) assert run.__class__ == RunNode assert run.duration == 480 + assert run.n_vols = 480 / 2 def test_get_nodes(layout1): diff --git a/bids/variables/tests/test_variables.py b/bids/variables/tests/test_variables.py index 54083989c..8c54a8926 100644 --- a/bids/variables/tests/test_variables.py +++ b/bids/variables/tests/test_variables.py @@ -19,7 +19,7 @@ def generate_DEV(name='test', sr=20, duration=480): ent_names = ['task', 'run', 'session', 'subject'] entities = {e: uuid.uuid4().hex for e in ent_names} image = uuid.uuid4().hex + '.nii.gz' - run_info = RunInfo(entities, duration, 2, image) + run_info = RunInfo(entities, duration, 2, image, duration / 2), return DenseRunVariable(name='test', values=values, run_info=run_info, source='dummy', sampling_rate=sr) diff --git a/bids/variables/variables.py b/bids/variables/variables.py index adb295b9e..29ea03c03 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -496,15 +496,50 @@ def split(self, grouper): source=self.source, sampling_rate=self.sampling_rate) for i, name in enumerate(df.columns)] + + + trs = {var.run_info[0].tr for var in self.variables.values()} + if not trs: + raise ValueError("Repetition time unavailable; specify " + "sampling_rate in Hz explicitly or set to" + " 'highest'.") + elif len(trs) > 1: + raise ValueError("Non-unique Repetition times found " + "({!r}); specify sampling_rate explicitly" + .format(trs)) + return 1. / trs.pop() + + def _get_sampling_rate(sampling_rate) + if sampling_rate == 'TR': + trs = {var.run_info[0].tr for var in self.variables.values()} + if not trs: + raise ValueError("Repetition time unavailable; specify " + "sampling_rate in Hz explicitly or set to" + " 'highest'.") + elif len(trs) > 1: + raise ValueError("Non-unique Repetition times found " + "({!r}); specify sampling_rate explicitly" + .format(trs)) + return 1. / trs.pop() + else: + return sampling_rate + + def _build_entity_index(self, run_info, sampling_rate): """Build the entity index from run information. """ index = [] - interval = int(round(1000. / sampling_rate)) _timestamps = [] for run in run_info: - reps = int(np.round(run.duration * sampling_rate)) + if sampling_rate == 'TR': + reps = run.n_vols + sr = run.tr + else: + sr = sampling_rate + reps = int(np.round(run.duration * sr)) + + interval = int(round(1000. / sr)) ent_vals = list(run.entities.values()) df = pd.DataFrame([ent_vals] * reps, columns=list(run.entities.keys())) ts = pd.date_range(0, periods=len(df), freq='%sms' % interval) @@ -532,8 +567,10 @@ def resample(self, sampling_rate, inplace=False, kind='linear'): var = self.clone() var.resample(sampling_rate, True, kind) return var + + sr = self._get_sampling_rate(sampling_rate) - if sampling_rate == self.sampling_rate: + if sr == self.sampling_rate: return n = len(self.index) @@ -543,7 +580,7 @@ def resample(self, sampling_rate, inplace=False, kind='linear'): x = np.arange(n) num = len(self.index) - if sampling_rate < self.sampling_rate: + if sr < self.sampling_rate: # Downsampling, so filter the signal from scipy.signal import butter, filtfilt # cutoff = new Nyqist / old Nyquist From 6d041df09c09e8921152f346c0beaee49ae8dc92 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Tue, 6 Apr 2021 18:38:52 -0500 Subject: [PATCH 03/14] Replace math.ceil --- bids/variables/variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/variables.py b/bids/variables/variables.py index 29ea03c03..0f5b32900 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -537,7 +537,7 @@ def _build_entity_index(self, run_info, sampling_rate): sr = run.tr else: sr = sampling_rate - reps = int(np.round(run.duration * sr)) + reps = int(math.ceil(run.duration * sr)) interval = int(round(1000. / sr)) ent_vals = list(run.entities.values()) From 0c454ce780c43f7a5bef8c6cdb9703fd24d665e5 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Tue, 6 Apr 2021 18:52:06 -0500 Subject: [PATCH 04/14] Fix paste error --- bids/variables/variables.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/bids/variables/variables.py b/bids/variables/variables.py index 0f5b32900..b7b46f8de 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -497,18 +497,7 @@ def split(self, grouper): sampling_rate=self.sampling_rate) for i, name in enumerate(df.columns)] - - trs = {var.run_info[0].tr for var in self.variables.values()} - if not trs: - raise ValueError("Repetition time unavailable; specify " - "sampling_rate in Hz explicitly or set to" - " 'highest'.") - elif len(trs) > 1: - raise ValueError("Non-unique Repetition times found " - "({!r}); specify sampling_rate explicitly" - .format(trs)) - return 1. / trs.pop() - + def _get_sampling_rate(sampling_rate) if sampling_rate == 'TR': trs = {var.run_info[0].tr for var in self.variables.values()} From 9f32e098cb9a817948e0d052b0dac3a56cc22b67 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 6 Apr 2021 21:06:11 -0400 Subject: [PATCH 05/14] Update bids/variables/variables.py --- bids/variables/variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/variables.py b/bids/variables/variables.py index b7b46f8de..b41f492ea 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -498,7 +498,7 @@ def split(self, grouper): for i, name in enumerate(df.columns)] - def _get_sampling_rate(sampling_rate) + def _get_sampling_rate(sampling_rate): if sampling_rate == 'TR': trs = {var.run_info[0].tr for var in self.variables.values()} if not trs: From aee735ca613d192065c9f76ddcf1d6719d600214 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 6 Apr 2021 21:10:47 -0400 Subject: [PATCH 06/14] Update bids/variables/io.py --- bids/variables/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/io.py b/bids/variables/io.py index c91400bbd..e20ec58bd 100644 --- a/bids/variables/io.py +++ b/bids/variables/io.py @@ -248,7 +248,7 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None, } run = dataset.create_node('run', entities, image_file=img_f, - duration=duration, repetition_time=trm + duration=duration, repetition_time=trm, n_vols=nvols) run_info = run.get_info() From 6cee73bffdd1153383439f88bfae53b190756582 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 6 Apr 2021 21:14:34 -0400 Subject: [PATCH 07/14] Update bids/variables/io.py --- bids/variables/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/io.py b/bids/variables/io.py index e20ec58bd..cd9c80225 100644 --- a/bids/variables/io.py +++ b/bids/variables/io.py @@ -248,7 +248,7 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None, } run = dataset.create_node('run', entities, image_file=img_f, - duration=duration, repetition_time=trm, + duration=duration, repetition_time=tr, n_vols=nvols) run_info = run.get_info() From 8ff3178d74d61da07e9e54acc6b9b78f07a4bf60 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 6 Apr 2021 21:20:22 -0400 Subject: [PATCH 08/14] Update bids/variables/tests/test_entities.py --- bids/variables/tests/test_entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/tests/test_entities.py b/bids/variables/tests/test_entities.py index 6dc0f59da..7966ee96f 100644 --- a/bids/variables/tests/test_entities.py +++ b/bids/variables/tests/test_entities.py @@ -48,7 +48,7 @@ def test_get_or_create_node(layout1): n_vols=480/2) assert run.__class__ == RunNode assert run.duration == 480 - assert run.n_vols = 480 / 2 + assert run.n_vols == 480 / 2 def test_get_nodes(layout1): From ed1a66c15c3da07f464eca49e5668f63eb3a49d6 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Tue, 6 Apr 2021 22:18:30 -0500 Subject: [PATCH 09/14] Add self to get sampling rate --- bids/variables/variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/variables.py b/bids/variables/variables.py index b41f492ea..2b4dbce4f 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -498,7 +498,7 @@ def split(self, grouper): for i, name in enumerate(df.columns)] - def _get_sampling_rate(sampling_rate): + def _get_sampling_rate(self, sampling_rate): if sampling_rate == 'TR': trs = {var.run_info[0].tr for var in self.variables.values()} if not trs: From f40fa19b1ab398f8182aa4262d27e1cc221873d7 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Wed, 7 Apr 2021 14:51:41 -0500 Subject: [PATCH 10/14] simplify tr checking --- bids/variables/collections.py | 22 +++++++++--- bids/variables/tests/test_collections.py | 1 - bids/variables/variables.py | 46 ++++++++---------------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/bids/variables/collections.py b/bids/variables/collections.py index 00cac9a22..0e1a74967 100644 --- a/bids/variables/collections.py +++ b/bids/variables/collections.py @@ -327,6 +327,18 @@ def _get_sampling_rate(self, sampling_rate): if isinstance(sampling_rate, (float, int)) or sampling_rate == 'TR': return sampling_rate + if sampling_rate == 'TR': + trs = {var.run_info[0].tr for var in self.variables.values()} + if not trs: + raise ValueError("Repetition time unavailable; specify " + "sampling_rate in Hz explicitly or set to" + " 'highest'.") + elif len(trs) > 1: + raise ValueError("Non-unique Repetition times found " + "({!r}); specify sampling_rate explicitly" + .format(trs)) + return 1. / trs.pop() + if sampling_rate.lower() == 'highest': dense_vars = self.get_dense_variables() # If no dense variables are available, fall back on instance SR @@ -345,7 +357,7 @@ def _densify_and_resample(self, sampling_rate=None, variables=None, resample_dense=False, force_dense=False, in_place=False, kind='linear'): - sampling_rate = self._get_sampling_rate(sampling_rate) + sr = self._get_sampling_rate(sampling_rate) _dense, _sparse = [], [] @@ -363,11 +375,13 @@ def _densify_and_resample(self, sampling_rate=None, variables=None, if force_dense: for v in _sparse: if is_numeric_dtype(v.values): - _variables[v.name] = v.to_dense(sampling_rate) + _variables[v.name] = v.to_dense(sr) if resample_dense: + # Propagate 'TR' if exact match to TR is required + sr_arg = sampling_rate if sampling_rate == 'TR' else sr for v in _dense: - _variables[v.name] = v.resample(sampling_rate, kind=kind) + _variables[v.name] = v.resample(sr_arg, kind=kind) coll = self if in_place else self.clone() @@ -376,7 +390,7 @@ def _densify_and_resample(self, sampling_rate=None, variables=None, else: coll.variables = _variables - coll.sampling_rate = sampling_rate + coll.sampling_rate = sr return coll def to_dense(self, sampling_rate=None, variables=None, in_place=False, diff --git a/bids/variables/tests/test_collections.py b/bids/variables/tests/test_collections.py index 7c68c6eb5..48d92e176 100644 --- a/bids/variables/tests/test_collections.py +++ b/bids/variables/tests/test_collections.py @@ -54,7 +54,6 @@ def test_run_variable_collection_dense_variable_accessors(run_coll): def test_run_variable_collection_get_sampling_rate(run_coll): coll = run_coll.clone() assert coll._get_sampling_rate(None) == 10 - assert coll._get_sampling_rate('TR') == 0.5 coll.variables['RT'].run_info[0] = RunInfo({}, 200, 10, None, 20) with pytest.raises(ValueError) as exc: coll._get_sampling_rate('TR') diff --git a/bids/variables/variables.py b/bids/variables/variables.py index 2b4dbce4f..bbda614f9 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -497,38 +497,19 @@ def split(self, grouper): sampling_rate=self.sampling_rate) for i, name in enumerate(df.columns)] - - def _get_sampling_rate(self, sampling_rate): - if sampling_rate == 'TR': - trs = {var.run_info[0].tr for var in self.variables.values()} - if not trs: - raise ValueError("Repetition time unavailable; specify " - "sampling_rate in Hz explicitly or set to" - " 'highest'.") - elif len(trs) > 1: - raise ValueError("Non-unique Repetition times found " - "({!r}); specify sampling_rate explicitly" - .format(trs)) - return 1. / trs.pop() - - else: - return sampling_rate - - - def _build_entity_index(self, run_info, sampling_rate): + def _build_entity_index(self, run_info, sampling_rate, match_vol=False): """Build the entity index from run information. """ index = [] _timestamps = [] for run in run_info: - if sampling_rate == 'TR': + if match_vol: + # If TR, fix reps to n_vols to ensure match reps = run.n_vols - sr = run.tr else: - sr = sampling_rate - reps = int(math.ceil(run.duration * sr)) + reps = int(math.ceil(run.duration * sampling_rate)) - interval = int(round(1000. / sr)) + interval = int(round(1000. / sampling_rate)) ent_vals = list(run.entities.values()) df = pd.DataFrame([ent_vals] * reps, columns=list(run.entities.keys())) ts = pd.date_range(0, periods=len(df), freq='%sms' % interval) @@ -557,19 +538,22 @@ def resample(self, sampling_rate, inplace=False, kind='linear'): var.resample(sampling_rate, True, kind) return var - sr = self._get_sampling_rate(sampling_rate) - - if sr == self.sampling_rate: + match_vol = False + if sampling_rate == 'TR' + match_vol = True + sampling_rate = 1. / self.run_info[0].tr + + if sampling_rate == self.sampling_rate: return - + n = len(self.index) - self.index = self._build_entity_index(self.run_info, sampling_rate) - + self.index = self._build_entity_index(self.run_info, sampling_rate, match_vol) + x = np.arange(n) num = len(self.index) - if sr < self.sampling_rate: + if sampling_rate < self.sampling_rate: # Downsampling, so filter the signal from scipy.signal import butter, filtfilt # cutoff = new Nyqist / old Nyquist From 7e0d9eaaa453243237ea2b90907be70c1668686a Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Wed, 7 Apr 2021 15:04:02 -0500 Subject: [PATCH 11/14] Fix typo --- bids/variables/tests/test_entities.py | 3 ++- bids/variables/variables.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bids/variables/tests/test_entities.py b/bids/variables/tests/test_entities.py index 7966ee96f..1d9f648d3 100644 --- a/bids/variables/tests/test_entities.py +++ b/bids/variables/tests/test_entities.py @@ -24,10 +24,11 @@ def layout2(): def test_run(layout1): img = layout1.get(subject='01', task='mixedgamblestask', suffix='bold', run=1, return_type='obj')[0] - run = RunNode(None, img.filename, 480, 2) + run = RunNode(None, img.filename, 480, 2, 480/2) assert run.image_file == img.filename assert run.duration == 480 assert run.repetition_time == 2 + assert run.n_vols == 480 / 2 def test_get_or_create_node(layout1): diff --git a/bids/variables/variables.py b/bids/variables/variables.py index bbda614f9..132518719 100644 --- a/bids/variables/variables.py +++ b/bids/variables/variables.py @@ -539,7 +539,7 @@ def resample(self, sampling_rate, inplace=False, kind='linear'): return var match_vol = False - if sampling_rate == 'TR' + if sampling_rate == 'TR': match_vol = True sampling_rate = 1. / self.run_info[0].tr From a6eafe48a500d2de686cf4ec3afe500518269fd1 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Wed, 7 Apr 2021 16:09:55 -0500 Subject: [PATCH 12/14] Fix tests --- bids/variables/collections.py | 2 +- bids/variables/io.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bids/variables/collections.py b/bids/variables/collections.py index 0e1a74967..bd1c330d0 100644 --- a/bids/variables/collections.py +++ b/bids/variables/collections.py @@ -324,7 +324,7 @@ def _get_sampling_rate(self, sampling_rate): if sampling_rate is None: return self.sampling_rate - if isinstance(sampling_rate, (float, int)) or sampling_rate == 'TR': + if isinstance(sampling_rate, (float, int)): return sampling_rate if sampling_rate == 'TR': diff --git a/bids/variables/io.py b/bids/variables/io.py index cd9c80225..60c2d594e 100644 --- a/bids/variables/io.py +++ b/bids/variables/io.py @@ -205,13 +205,13 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None, except Exception as e: if scan_length is not None: duration = scan_length + nvols = int(scan_length / tr) else: msg = ("Unable to extract scan duration from one or more " "BOLD runs, and no scan_length argument was provided " "as a fallback. Please check that the image files are " "available, or manually specify the scan duration.") raise ValueError(msg) from e - nvols = None # We don't want to pass all the image file's entities onto get_node(), # as there can be unhashable nested slice timing values, and this also From 6b65695c27d37fc1e65b96d167a0878f2989223b Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Wed, 7 Apr 2021 17:01:01 -0500 Subject: [PATCH 13/14] Update bids/variables/io.py Co-authored-by: Chris Markiewicz --- bids/variables/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/variables/io.py b/bids/variables/io.py index 60c2d594e..6c23a0df8 100644 --- a/bids/variables/io.py +++ b/bids/variables/io.py @@ -205,7 +205,7 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None, except Exception as e: if scan_length is not None: duration = scan_length - nvols = int(scan_length / tr) + nvols = int(np.rint(scan_length / tr)) else: msg = ("Unable to extract scan duration from one or more " "BOLD runs, and no scan_length argument was provided " From c0551ea49b2246cbeda04a90e813b488ebbb27a2 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Wed, 7 Apr 2021 17:46:57 -0500 Subject: [PATCH 14/14] Add test w/ slight error in scan_length --- bids/variables/tests/test_collections.py | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bids/variables/tests/test_collections.py b/bids/variables/tests/test_collections.py index 48d92e176..a608accd0 100644 --- a/bids/variables/tests/test_collections.py +++ b/bids/variables/tests/test_collections.py @@ -1,6 +1,7 @@ from os.path import join, dirname, abspath import pytest +import numpy as np from bids.layout import BIDSLayout from bids.tests import get_test_data_path @@ -16,6 +17,14 @@ def run_coll(): return layout.get_collections('run', types=['events'], merge=True, scan_length=480, subject=['01', '02', '04']) +@pytest.fixture(scope="module") +def run_coll_bad_length(): + path = join(get_test_data_path(), 'ds005') + layout = BIDSLayout(path) + # Limit to a few subjects to reduce test running time + return layout.get_collections('run', types=['events'], merge=True, + scan_length=480.1, subject=['01', '02', '04']) + @pytest.fixture(scope="module") def run_coll_list(): @@ -203,6 +212,34 @@ def test_run_variable_collection_to_df_all_dense_vars(run_coll): n_rows = int(rows_per_var * 12 / 10) assert df.shape == (n_rows, 18) +def test_run_variable_collection_bad_length_to_df_all_dense_vars(run_coll_bad_length): + + timing_cols = {'onset', 'duration'} + entity_cols = {'subject', 'run', 'task', 'suffix', 'datatype'} + cond_names = {'PTval', 'RT', 'gain', 'loss', 'parametric gain', 'respcat', + 'respnum', 'trial_type'} + md_names = {'TaskName', 'RepetitionTime', 'extension', 'SliceTiming'} + condition = {'condition'} + ampl = {'amplitude'} + + unif_coll = run_coll_bad_length.to_dense(sampling_rate=10) + + df = unif_coll.to_df() + rows_per_var = np.round(3 * 3 * 480.1 * 10) # subjects x runs x time x sampling rate + + # Test resampling without setting sample rate (default sr == 10) + df = unif_coll.to_df(format='long') + assert df.shape == (rows_per_var * 7, 13) + cols = timing_cols | entity_cols | condition | ampl | md_names + assert set(df.columns) == cols + + # Test resampling to TR + df = unif_coll.to_df(sampling_rate='TR') + n_rows = int(480 * 3 * 3 / 2) # (Note number of volumes is 480, not 480.1) + assert df.shape == (n_rows, 18) + cols = (timing_cols | entity_cols | cond_names | md_names) - {'trial_type'} + assert set(df.columns) == cols + def test_run_variable_collection_to_df_mixed_vars(run_coll): coll = run_coll.clone()