Skip to content

Commit 0c56823

Browse files
committed
More robust handling of events input data
1 parent eb0ee18 commit 0c56823

File tree

3 files changed

+25
-15
lines changed

3 files changed

+25
-15
lines changed

bidscoin/bidseditor.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import copy
77
import webbrowser
88
import ast
9+
import re
910
import json
1011
import csv
1112
import nibabel as nib
@@ -1673,13 +1674,17 @@ def events_rows2run(self, rowindex: int, colindex: int):
16731674
if mapping:
16741675
try:
16751676
mapping = ast.literal_eval(mapping) # Convert stringified dict back to dict
1677+
for key, pattern in mapping.items():
1678+
re.compile(pattern)
16761679
LOGGER.verbose(f"User sets events['rows'][{rowindex}] to {mapping}' for {self.target_run}")
16771680
if rowindex == nrows - 1:
16781681
self.target_run.events['rows'].append({'condition' if colindex==0 else 'cast': mapping})
16791682
else:
16801683
self.target_run.events['rows'][rowindex]['condition' if colindex==0 else 'cast'] = mapping
1681-
except (ValueError, SyntaxError):
1682-
QMessageBox.warning(self, 'Input error', f"Please enter a valid '{mapping}' dictionary")
1684+
except (ValueError, SyntaxError) as dict_error:
1685+
QMessageBox.warning(self, 'Input error', f"Please enter a valid '{mapping}' dictionary\n\n{dict_error}")
1686+
except re.error as pattern_error:
1687+
QMessageBox.warning(self, 'Input error', f"Please enter a valid '{mapping}' pattern:\n\n{pattern_error}")
16831688
elif colindex == 0 and rowindex < nrows - 1: # Remove the row
16841689
del self.target_run.events['rows'][rowindex]
16851690
else:
@@ -1700,7 +1705,7 @@ def events_columns2run(self, rowindex: int, colindex: int):
17001705
output = self.events_columns.item(rowindex, 1).text().strip() if self.events_columns.item(rowindex, 1) else ''
17011706
nrows = self.events_columns.rowCount()
17021707

1703-
if input and not output:
1708+
if colindex == 0 and input and not output:
17041709
output = input
17051710

17061711
if not input or input in self.target_run.eventsparser().logtable:

bidscoin/plugins/__init__.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,22 +193,24 @@ def eventstable(self) -> pd.DataFrame:
193193

194194
# Check the parser's data structure
195195
if not self.isvalid:
196-
return pd.DataFrame()
196+
pass
197197

198198
df = copy.deepcopy(self.logtable)
199199

200200
# Convert the timing values to seconds (with maximally 4 digits after the decimal point)
201-
df[self.time['cols']] = (df[self.time['cols']].apply(pd.to_numeric, errors='coerce') / self.time['unit']).round(4)
201+
timecols = [col for col in self.time.get('cols',[]) if col in df.columns]
202+
df[timecols] = (df[timecols].apply(pd.to_numeric, errors='coerce') / self.time['unit']).round(4)
202203

203204
# Take the logtable columns of interest and from now on use the BIDS column names
204-
df = df.loc[:, [sourcecol for item in self.columns for sourcecol in item.values() if sourcecol]]
205-
df.columns = [eventscol for item in self.columns for eventscol, sourcecol in item.items() if sourcecol]
205+
df = df.loc[:, [sourcecol for item in self.columns for sourcecol in item.values() if sourcecol in df.columns]]
206+
df.columns = [eventscol for item in self.columns for eventscol, sourcecol in item.items() if sourcecol in df.columns]
206207

207208
# Set the clock at zero at the start of the experiment
208209
if self.time.get('start'):
209210
start = pd.Series([True] * len(df))
210211
for column, value in self.time['start'].items():
211-
start &= (self.logtable[column].astype(str) == str(value)).values
212+
if column in self.logtable.columns:
213+
start &= (self.logtable[column].astype(str) == str(value)).values
212214
if start.any():
213215
LOGGER.bcdebug(f"Resetting clock offset: {df['onset'][start.values].iloc[0]}")
214216
df['onset'] -= df['onset'][start.values].iloc[0] # Take the time of the first occurrence as zero
@@ -219,6 +221,9 @@ def eventstable(self) -> pd.DataFrame:
219221

220222
for column, regex in group['condition'].items():
221223

224+
if column not in self.logtable.columns:
225+
continue
226+
222227
# Get the rows that match the expression, i.e. make them True
223228
rowgroup = self.logtable[column].astype(str).str.fullmatch(str(regex))
224229

@@ -294,7 +299,7 @@ def is_float(s):
294299
for name in set([name for item in self.columns for name in item.values()] + [name for item in self.rows for name in item['condition'].keys()] +
295300
[*self.time.get('start', {}).keys()] + self.time.get('cols', [])):
296301
if name and name not in columns:
297-
LOGGER.warning(f"Column '{name}' not found in the event table of {self}")
302+
LOGGER.warning(f"Column '{name}' not found in the input table parsed from {self}")
298303
valid = False
299304
if columns.duplicated().any():
300305
LOGGER.warning(f"Duplicate columns found: {columns}\n{self}")

bidscoin/plugins/events2bids.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,18 +196,18 @@ def logtable(self) -> pd.DataFrame:
196196

197197
# Get the row indices to slice the event, stimulus, video or survey table
198198
df.columns = self._sourcecols
199-
if self.options['table'] == 'event':
199+
if self.options['table'].lower() == 'event':
200200
begin = 0
201201
end = min(stimulus_header, video_header, survey_header)
202-
elif self.options['table'] == 'stimulus':
202+
elif self.options['table'].lower() == 'stimulus':
203203
df.columns = df.iloc[stimulus_header]
204204
begin = stimulus_header + 1
205205
end = min(video_header, survey_header)
206-
elif self.options['table'] == 'video':
206+
elif self.options['table'].lower() == 'video':
207207
df.columns = df.iloc[video_header]
208208
begin = video_header + 1
209209
end = survey_header
210-
elif self.options['table'] == 'survey':
210+
elif self.options['table'].lower() == 'survey':
211211
df.columns = df.iloc[survey_header]
212212
begin = survey_header + 1
213213
end = nrows
@@ -222,11 +222,11 @@ def logtable(self) -> pd.DataFrame:
222222
for i, col in enumerate(df.columns):
223223
if pd.isna(col) or col == '': # Check if the column name is NaN or an empty string
224224
cols.append(new_col := f"unknown_{i}")
225-
LOGGER.info(f"Renaming empty column name at index {i}: {col} -> {new_col}")
225+
LOGGER.bcdebug(f"Renaming empty column name at index {i}: {col} -> {new_col}")
226226
elif col in dupl: # If duplicate, append the index number
227227
dupl[col] += 1
228228
cols.append(new_col := f"{col}_{dupl[col]}")
229-
LOGGER.info(f"Renaming duplicate column name: {col} -> {new_col}")
229+
LOGGER.bcdebug(f"Renaming duplicate column name: {col} -> {new_col}")
230230
else: # First occurrence of the column name, add it to dupl
231231
dupl[col] = 0
232232
cols.append(col)

0 commit comments

Comments
 (0)