Skip to content

Commit 3bb2437

Browse files
committed
Merge remote-tracking branch 'upstream/master' into rel/0.7.1
2 parents b049fc7 + 6984cdd commit 3bb2437

File tree

8 files changed

+72
-44
lines changed

8 files changed

+72
-44
lines changed

bids/analysis/tests/test_analysis.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,28 @@ def test_get_design_matrix_arguments(analysis):
3131
kwargs = dict(run=1, subject='01', sparse=True)
3232
result = analysis['run'].get_design_matrix(**kwargs)
3333
result = result[0]
34-
assert result.sparse.shape == (172, 7)
34+
assert result.sparse.shape == (172, 9)
3535
assert result.dense is None
3636

3737
kwargs = dict(run=1, subject='01', mode='dense', force=False)
3838
result = analysis['run'].get_design_matrix(**kwargs)[0]
3939
assert result.sparse is None
4040
assert result.dense is None
4141

42-
kwargs = dict(run=1, subject='01', mode='dense', force=True, sampling_rate='highest')
42+
kwargs = dict(run=1, subject='01', mode='dense', force=True,
43+
sampling_rate='highest')
4344
result = analysis['run'].get_design_matrix(**kwargs)[0]
4445
assert result.sparse is None
4546
assert result.dense.shape == (4800, 6)
4647

47-
kwargs = dict(run=1, subject='01', mode='dense', force=True, sampling_rate='TR')
48+
kwargs = dict(run=1, subject='01', mode='dense', force=True,
49+
sampling_rate='TR')
4850
result = analysis['run'].get_design_matrix(**kwargs)[0]
4951
assert result.sparse is None
5052
assert result.dense.shape == (240, 6)
5153

52-
kwargs = dict(run=1, subject='01', mode='dense', force=True, sampling_rate=0.5)
54+
kwargs = dict(run=1, subject='01', mode='dense', force=True,
55+
sampling_rate=0.5)
5356
result = analysis['run'].get_design_matrix(**kwargs)[0]
5457
assert result.sparse is None
5558
assert result.dense.shape == (240, 6)
@@ -72,11 +75,11 @@ def test_first_level_sparse_design_matrix(analysis):
7275
result = analysis['run'].get_design_matrix(subject=['01'])
7376
assert len(result) == 3
7477
df = result[0].sparse
75-
assert df.shape == (172, 7)
78+
assert df.shape == (172, 9)
7679
assert df['condition'].nunique() == 2
7780
assert set(result[0][0].columns) == {'amplitude', 'onset', 'duration',
7881
'condition', 'subject', 'run',
79-
'task'}
82+
'task', 'datatype', 'suffix'}
8083

8184

8285
def test_post_first_level_sparse_design_matrix(analysis):
@@ -87,7 +90,9 @@ def test_post_first_level_sparse_design_matrix(analysis):
8790
assert result[0].sparse.shape == (9, 2)
8891
assert result[0].entities == {
8992
'subject': '01',
90-
'task': 'mixedgamblestask'}
93+
'task': 'mixedgamblestask',
94+
'datatype': 'func',
95+
'suffix': 'bold'}
9196

9297
# Participant level and also check integer-based indexing
9398
result = analysis['participant'].get_design_matrix()

bids/layout/layout.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,14 @@ def _validate_file(self, f):
305305
if not self.validate:
306306
return True
307307

308-
# For derivatives, we need to cheat a bit and construct a fake
309-
# derivatives path--prepend 'derivatives' and the pipeline name
310-
to_check = os.path.relpath(f, self.root)
308+
# Derivatives are currently not validated.
311309
if 'derivatives' in self.domains:
312-
to_check = os.path.join(
313-
'derivatives', self.description['PipelineDescription']['Name'],
314-
to_check)
310+
return True
315311

316-
sep = os.path.sep
317-
if to_check[:len(sep)] != sep:
318-
to_check = sep + to_check
312+
# BIDS validator expects absolute paths, but really these are relative
313+
# to the BIDS project root.
314+
to_check = os.path.relpath(f, self.root)
315+
to_check = os.path.join(os.path.sep, to_check)
319316

320317
return self.validator.is_bids(to_check)
321318

bids/variables/io.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def _load_time_variables(layout, dataset=None, columns=None, scan_length=None,
201201
# Add in all of the run's entities as new columns for
202202
# index
203203
for entity, value in entities.items():
204-
if entity in BASE_ENTITIES:
204+
if entity in ALL_ENTITIES:
205205
df[entity] = value
206206

207207
if drop_na:
@@ -327,14 +327,20 @@ def _load_tsv_variables(layout, suffix, dataset=None, columns=None,
327327
# file (for entities that vary by row), or from the full file path
328328
# (for entities constant over all rows in the file). We extract both
329329
# and store them in the main DataFrame alongside other variables (as
330-
# they'll be extracted when the Column is initialized anyway).
330+
# they'll be extracted when the BIDSVariable is initialized anyway).
331331
for ent_name, ent_val in f.entities.items():
332-
if ent_name in BASE_ENTITIES:
332+
if ent_name in ALL_ENTITIES:
333333
_data[ent_name] = ent_val
334334

335335
# Handling is a bit more convoluted for scans.tsv, because the first
336336
# column contains the run filename, which we also need to parse.
337337
if suffix == 'scans':
338+
339+
# Suffix is guaranteed to be present in each filename, so drop the
340+
# constant column with value 'scans' to make way for it and prevent
341+
# two 'suffix' columns.
342+
_data.drop(columns='suffix', inplace=True)
343+
338344
image = _data['filename']
339345
_data = _data.drop('filename', axis=1)
340346
dn = f.dirname
@@ -369,12 +375,11 @@ def make_patt(x, regex_search=False):
369375
# Filter rows on all selectors
370376
comm_cols = list(set(_data.columns) & set(selectors.keys()))
371377
for col in comm_cols:
372-
for val in listify(selectors.get(col)):
373-
ent_patts = [make_patt(x, regex_search=layout.regex_search)
374-
for x in listify(selectors.get(col))]
375-
patt = '|'.join(ent_patts)
378+
ent_patts = [make_patt(x, regex_search=layout.regex_search)
379+
for x in listify(selectors.get(col))]
380+
patt = '|'.join(ent_patts)
376381

377-
_data = _data[_data[col].str.contains(patt)]
382+
_data = _data[_data[col].str.contains(patt)]
378383

379384
level = {'scans': 'session', 'sessions': 'subject',
380385
'participants': 'dataset'}[suffix]

bids/variables/tests/test_collections.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,35 +57,27 @@ def test_run_variable_collection_to_df(run_coll):
5757

5858
# All variables sparse, wide format
5959
df = run_coll.to_df()
60-
assert df.shape == (4096, 13)
60+
assert df.shape == (4096, 15)
6161
wide_cols = {'onset', 'duration', 'subject', 'run', 'task',
6262
'PTval', 'RT', 'gain', 'loss', 'parametric gain', 'respcat',
63-
'respnum', 'trial_type'}
63+
'respnum', 'trial_type', 'suffix', 'datatype'}
6464
assert set(df.columns) == wide_cols
6565

6666
# All variables sparse, wide format
6767
df = run_coll.to_df(format='long')
68-
assert df.shape == (32768, 7)
68+
assert df.shape == (32768, 9)
6969
long_cols = {'amplitude', 'duration', 'onset', 'condition', 'run',
70-
'task', 'subject'}
70+
'task', 'subject', 'suffix', 'datatype'}
7171
assert set(df.columns) == long_cols
7272

7373
# All variables dense, wide format
7474
df = run_coll.to_df(sparse=False)
7575
assert df.shape == (230400, 14)
76-
# The inclusion of 'modality' and 'type' here is a minor bug that should
77-
# be fixed at some point. There is no reason why to_df() should return
78-
# more columns for a DenseRunVariable than a SparseRunVariable, but this
79-
# is happening because these columns are not included in the original
80-
# SparseRunVariable data, and are being rebuilt from the entity list in
81-
# the DenseRunVariable init.
82-
wide_cols |= {'datatype', 'suffix'}
8376
assert set(df.columns) == wide_cols - {'trial_type'}
8477

8578
# All variables dense, wide format
8679
df = run_coll.to_df(sparse=False, format='long')
8780
assert df.shape == (1612800, 9)
88-
long_cols |= {'datatype', 'suffix'}
8981
assert set(df.columns) == long_cols
9082

9183

@@ -100,14 +92,14 @@ def test_merge_collections(run_coll, run_coll_list):
10092
def test_get_collection_entities(run_coll_list):
10193
coll = run_coll_list[0]
10294
ents = coll.entities
103-
assert {'run', 'task', 'subject'} == set(ents.keys())
95+
assert {'run', 'task', 'subject', 'suffix', 'datatype'} == set(ents.keys())
10496

10597
merged = merge_collections(run_coll_list[:3])
10698
ents = merged.entities
107-
assert {'task', 'subject'} == set(ents.keys())
99+
assert {'task', 'subject', 'suffix', 'datatype'} == set(ents.keys())
108100
assert ents['subject'] == '01'
109101

110102
merged = merge_collections(run_coll_list[3:6])
111103
ents = merged.entities
112-
assert {'task', 'subject'} == set(ents.keys())
104+
assert {'task', 'subject', 'suffix', 'datatype'} == set(ents.keys())
113105
assert ents['subject'] == '02'

bids/variables/tests/test_entities.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_get_collections_merged(layout1):
6969
vals = collection.variables['RT'].values
7070
ents = collection.variables['RT'].index
7171
assert len(ents) == len(vals) == 4096
72-
assert set(ents.columns) == {'task', 'run', 'subject'}
72+
assert set(ents.columns) == {'task', 'run', 'subject', 'suffix', 'datatype'}
7373

7474

7575
def test_get_collections_unmerged(layout2):

bids/variables/tests/test_io.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def test_load_events(layout1):
3838
targ_cols = {'parametric gain', 'PTval', 'trial_type', 'respnum'}
3939
assert not (targ_cols - set(variables.keys()))
4040
assert isinstance(variables['parametric gain'], SparseRunVariable)
41-
assert variables['parametric gain'].index.shape == (86, 3)
41+
assert variables['parametric gain'].index.shape == (86, 5)
4242
assert variables['parametric gain'].source == 'events'
4343

4444

@@ -51,12 +51,12 @@ def test_load_participants(layout1):
5151
assert {'age', 'sex'} == set(dataset.variables.keys())
5252
age = dataset.variables['age']
5353
assert isinstance(age, SimpleVariable)
54-
assert age.index.shape == (16, 1)
54+
assert age.index.shape == (16, 2)
5555
assert age.values.shape == (16,)
5656

5757
index = load_variables(layout1, types='participants', subject=['^1.*'])
5858
age = index.get_nodes(level='dataset')[0].variables['age']
59-
assert age.index.shape == (7, 1)
59+
assert age.index.shape == (7, 2)
6060
assert age.values.shape == (7,)
6161

6262

bids/variables/tests/test_variables.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from bids.layout import BIDSLayout
22
import pytest
3+
import os
34
from os.path import join
45
from bids.tests import get_test_data_path
56
from bids.variables import (merge_variables, DenseRunVariable, SimpleVariable,
67
load_variables)
78
from bids.variables.entities import RunInfo
89
import numpy as np
910
import pandas as pd
11+
import nibabel as nb
1012
import uuid
13+
import json
1114

1215

1316
def generate_DEV(name='test', sr=20, duration=480):
@@ -174,3 +177,28 @@ def test_filter_simple_variable(layout2):
174177
assert merged.filter({'nonexistent': 2}, strict=True) is None
175178
merged.filter({'acquisition': 'fullbrain'}, inplace=True)
176179
assert merged.to_df().shape == (40, 9)
180+
181+
182+
@pytest.mark.parametrize(
183+
"TR, nvols",
184+
[(2.00000, 251),
185+
(2.000001, 251)])
186+
def test_resampling_edge_case(tmpdir, TR, nvols):
187+
tmpdir.chdir()
188+
os.makedirs('sub-01/func')
189+
with open('sub-01/func/sub-01_task-task_events.tsv', 'w') as fobj:
190+
fobj.write('onset\tduration\tval\n1\t0.1\t1\n')
191+
with open('sub-01/func/sub-01_task-task_bold.json', 'w') as fobj:
192+
json.dump({'RepetitionTime': TR}, fobj)
193+
194+
dataobj = np.zeros((5, 5, 5, nvols), dtype=np.int16)
195+
affine = np.diag((2.5, 2.5, 2.5, 1))
196+
img = nb.Nifti1Image(dataobj, affine)
197+
img.header.set_zooms((2.5, 2.5, 2.5, TR))
198+
img.to_filename('sub-01/func/sub-01_task-task_bold.nii.gz')
199+
200+
layout = BIDSLayout('.', validate=False)
201+
coll = load_variables(layout).get_collections('run')[0]
202+
dense_var = coll.variables['val'].to_dense(coll.sampling_rate)
203+
regressor = dense_var.resample(1.0 / TR).values
204+
assert regressor.shape == (nvols, 1)

bids/variables/variables.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,12 +448,13 @@ def resample(self, sampling_rate, inplace=False, kind='linear'):
448448
self.index = self._build_entity_index(self.run_info, sampling_rate)
449449

450450
x = np.arange(n)
451-
num = int(np.ceil(n * sampling_rate / old_sr))
451+
num = len(self.index)
452452

453453
from scipy.interpolate import interp1d
454454
f = interp1d(x, self.values.values.ravel(), kind=kind)
455455
x_new = np.linspace(0, n - 1, num=num)
456456
self.values = pd.DataFrame(f(x_new))
457+
assert len(self.values) == len(self.index)
457458

458459
self.sampling_rate = sampling_rate
459460

0 commit comments

Comments
 (0)