Skip to content

Commit 367fd17

Browse files
pvelascoyarikoptic
authored andcommitted
ENH: First version of the BIDSFile class
I just copied the code from https://github.com/cbinyu/heudiconv/tree/BIDSFile_helper. It still needs work.
1 parent de07958 commit 367fd17

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

heudiconv/bids/schema.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,150 @@ class BIDSFile:
2424
_known_entities = _load_entities_order()
2525

2626

27+
28+
def __init__(self, entities, suffix, extension):
29+
self._entities = entities
30+
self._suffix = suffix
31+
self._extension = extension
32+
33+
def __eq__(self, other):
34+
if not isinstance(other, self.__class__):
35+
return False
36+
if (
37+
all([other[k] == v for k, v in self._entities.items()])
38+
and self.extension == other.extension
39+
and self.suffix == other.suffix
40+
):
41+
return True
42+
else:
43+
return False
44+
45+
@classmethod
46+
def parse(cls, filename):
47+
""" Parse the filename for BIDS entities, suffix and extension """
48+
# use re.findall to find all lower-case-letters + '-' + alphanumeric + '_' pairs:
49+
entities_list = re.findall('([a-z]+)-([a-zA-Z0-9]+)[_]*', filename)
50+
# keep only those in the _known_entities list:
51+
entities = {k: v for k, v in entities_list if k in BIDSFile._known_entities}
52+
# get whatever comes after the last key-value pair, and remove any '_' that
53+
# might come in front:
54+
ending = filename.split('-'.join(entities_list[-1]))[-1]
55+
ending = remove_prefix(ending, '_')
56+
# the first dot ('.') separates the suffix from the extension:
57+
if '.' in ending:
58+
suffix, extension = ending.split('.', 1)
59+
else:
60+
suffix, extension = ending, None
61+
return BIDSFile(entities, suffix, extension)
62+
63+
def __str__(self):
64+
""" reconstitute in a legit BIDS filename using the order from entity table """
65+
if 'sub' not in self._entities:
66+
raise ValueError('The \'sub-\' entity is mandatory')
67+
# reconstitute the ending for the filename:
68+
suffix = '_' + self.suffix if self.suffix else ''
69+
extension = '.' + self.extension if self.extension else ''
70+
return '_'.join(
71+
['-'.join([e, self._entities[e]]) for e in self._known_entities if e in self._entities]
72+
) + suffix + extension
73+
74+
def __getitem__(self, entity):
75+
return self._entities[entity] if entity in self._entities else None
76+
77+
def __setitem__(self, entity, value): # would puke with some exception if already known
78+
return self.set(entity, value, overwrite=False)
79+
80+
def set(self, entity, value, overwrite=True):
81+
if entity not in self._entities:
82+
# just set it; no complains here
83+
self._entities[entity] = value
84+
elif overwrite:
85+
lgr.warning("Overwriting the entity %s from %s to %s for file %s",
86+
str(entity),
87+
str(self[entity]),
88+
str(value),
89+
self.__str__()
90+
)
91+
self._entities[entity] = value
92+
else:
93+
# if it already exists, and overwrite is false:
94+
lgr.warning("Setting the entity %s to %s for file %s failed",
95+
str(entity),
96+
str(value),
97+
self.__str__()
98+
)
99+
100+
@property # as needed make them RW
101+
def suffix(self):
102+
return self._suffix
103+
104+
@property
105+
def extension(self):
106+
return self._extension
107+
27108
# TEMP: just for now, could be moved/removed
28109
def test_BIDSFile():
110+
""" Tests for the BIDSFile class """
111+
112+
# define entities in the correct order:
113+
sorted_entities = [
114+
('sub', 'Jason'),
115+
('acq', 'Treadstone'),
116+
('run', '2'),
117+
('echo', '1'),
118+
]
119+
# 'sub-Jason_acq-Treadstone_run-2_echo-1':
120+
expected_sorted_str = '_'.join(['-'.join(e) for e in sorted_entities])
121+
# expected BIDSFile:
122+
suffix = 'T1w'
123+
extension = 'nii.gz'
124+
expected_bids_file = BIDSFile(OrderedDict(sorted_entities), suffix, extension)
125+
126+
# entities in random order:
127+
idcs = list(range(len(sorted_entities)))
128+
shuffle(idcs)
129+
shuffled_entities = [sorted_entities[i] for i in idcs]
130+
shuffled_str = '_'.join(['-'.join(e) for e in shuffled_entities])
131+
132+
# Test __eq__ method.
133+
# It should consider equal BIDSFiles with the same entities even in different order:
134+
assert BIDSFile(OrderedDict(shuffled_entities), suffix, extension) == expected_bids_file
135+
136+
# Test __getitem__:
137+
assert all([expected_bids_file[k] == v for k, v in shuffled_entities])
138+
139+
# Test filename parser and __str__ method:
140+
# Note: the __str__ method should return entities in the correct order
141+
ending = '_T1w.nii.gz' # suffix + extension
142+
my_bids_file = BIDSFile.parse(shuffled_str + ending)
143+
assert my_bids_file == expected_bids_file
144+
assert str(my_bids_file) == expected_sorted_str + ending
145+
146+
ending = '.json' # just extension
147+
my_bids_file = BIDSFile.parse(shuffled_str + ending)
148+
assert my_bids_file.suffix == ''
149+
assert str(my_bids_file) == expected_sorted_str + ending
150+
151+
ending = '_T1w' # just suffix
152+
my_bids_file = BIDSFile.parse(shuffled_str + ending)
153+
assert my_bids_file.extension is None
154+
assert str(my_bids_file) == expected_sorted_str + ending
155+
156+
# Complain if entity 'sub' is not set:
157+
with pytest.raises(ValueError) as e_info:
158+
assert str(BIDSFile.parse('dir-reversed.json'))
159+
assert 'sub-' in e_info.value
160+
161+
# Test set method:
162+
# -for a new entity, just set it without a complaint:
163+
my_bids_file['dir'] = 'AP'
164+
assert my_bids_file['dir'] == 'AP'
165+
# -for an existing entity, don't change it by default:
166+
my_bids_file['echo'] = '2'
167+
assert my_bids_file['echo'] == expected_bids_file['echo'] # still the original value
168+
# -for an existing entity, you can overwrite it with "set":
169+
my_bids_file.set('echo', '2')
170+
assert my_bids_file['echo'] == '2'
171+
29172
assert BIDSFile._known_entities[:2] == ['sub', 'ses']
30173
print(BIDSFile._known_entities)

0 commit comments

Comments
 (0)