@@ -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
28109def 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