@@ -102,15 +102,14 @@ def eventstable(self) -> pd.DataFrame:
102102 if not self .isvalid :
103103 return pd .DataFrame ()
104104
105- # Take the columns of interest from the logtable and rename them
106105 df = copy .deepcopy (self .logtable )
107106
108107 # Convert the timing values to seconds (with maximally 4 digits after the decimal point)
109108 df [self .time ['cols' ]] = (df [self .time ['cols' ]].apply (pd .to_numeric , errors = 'coerce' ) / self .time ['unit' ]).round (4 )
110109
111- # Take the columns of interest and from now on use the BIDS column names
112- df = df .loc [:, [name for item in self .columns for name in item .values ()]]
113- df .columns = [name for item in self .columns for name in item .keys () ]
110+ # Take the logtable columns of interest and from now on use the BIDS column names
111+ df = df .loc [:, [sourcecol for item in self .columns for sourcecol in item .values () if sourcecol ]]
112+ df .columns = [eventscol for item in self .columns for eventscol , sourcecol in item .items () if sourcecol ]
114113
115114 # Set the clock at zero at the start of the experiment
116115 if self .time .get ('start' ):
@@ -121,22 +120,22 @@ def eventstable(self) -> pd.DataFrame:
121120 df ['onset' ] = df ['onset' ] - df ['onset' ][start ].iloc [0 ] # Take the time of the first occurrence as zero
122121
123122 # Loop over the row groups to filter/edit the rows
124- rows = pd .Series ([len (self .rows ) == 0 ] * len (df )).astype (bool ) # Series with True values if no row expressions were specified
123+ rows = pd .Series ([len (self .rows ) == 0 ] * len (df )).astype (bool ) # Boolean series with True values if no row expressions were specified
125124 for group in self .rows :
126125
127126 for column , regex in group ['include' ].items ():
128127
129- # Get the rows that match the expression
128+ # Get the rows that match the expression, i.e. make them True
130129 rowgroup = self .logtable [column ].astype (str ).str .fullmatch (str (regex ))
131130
132131 # Add the matching rows to the grand rows group
133- rows |= rowgroup
132+ rows |= rowgroup . values
134133
135134 # Write the value(s) of the matching rows
136- for newcolumn , newvalue in (group .get ('cast' ) or {}).items ():
137- df .loc [rowgroup , newcolumn ] = newvalue
135+ for colname , values in (group .get ('cast' ) or {}).items ():
136+ df .loc [rowgroup , colname ] = values
138137
139- return df .loc [rows ].sort_values (by = 'onset' )
138+ return df .loc [rows . values ].sort_values (by = 'onset' )
140139
141140 @property
142141 def columns (self ) -> List [dict ]:
@@ -177,30 +176,35 @@ def is_float(s):
177176 return False
178177
179178 if not (valid := len (self .columns ) >= 2 ):
180- LOGGER .warning (f"Events table must have at least two columns, got { len (self .columns )} instead" )
179+ LOGGER .warning (f"Events table must have at least two columns, got { len (self .columns )} instead\n { self } " )
181180 return False
182181
183182 if (key := [* self .columns [0 ].keys ()][0 ]) != 'onset' :
184- LOGGER .warning (f"First events column must be named 'onset', got '{ key } ' instead" )
183+ LOGGER .warning (f"First events column must be named 'onset', got '{ key } ' instead\n { self } " )
185184 valid = False
186185
187186 if (key := [* self .columns [1 ].keys ()][0 ]) != 'duration' :
188- LOGGER .warning (f"Second events column must be named 'duration', got '{ key } ' instead" )
187+ LOGGER .warning (f"Second events column must be named 'duration', got '{ key } ' instead\n { self } " )
189188 valid = False
190189
191190 if len (self .time .get ('cols' ,[])) < 2 :
192- LOGGER .warning (f"Events table must have at least two timecol items, got { len (self .time .get ('cols' ,[]))} instead" )
191+ LOGGER .warning (f"Events table must have at least two timecol items, got { len (self .time .get ('cols' ,[]))} instead\n { self } " )
193192 return False
194193
195194 elif not is_float (self .time .get ('unit' )):
196- LOGGER .warning (f"Time conversion factor must be a float, got '{ self .time .get ('unit' )} ' instead" )
195+ LOGGER .warning (f"Time conversion factor must be a float, got '{ self .time .get ('unit' )} ' instead\n { self } " )
197196 valid = False
198197
198+ # Check if the logtable has existing and unique column names
199+ df = self .logtable
199200 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 ['include' ].keys ()] +
200201 [* self .time .get ('start' ,{}).keys ()] + self .time .get ('cols' ,[])):
201- if name not in self . logtable :
202- LOGGER .warning (f"Column '{ name } ' not found in the event table of { self . sourcefile } " )
202+ if name and name not in df :
203+ LOGGER .warning (f"Column '{ name } ' not found in the event table of { self } " )
203204 valid = False
205+ if not df .columns [df .columns .duplicated ()].empty :
206+ LOGGER .warning (f"Duplicate columns found in: { df .columns } \n { self } " )
207+ valid = False
204208
205209 return valid
206210
@@ -677,7 +681,7 @@ def check(self, checks: Tuple[bool, bool, bool]=(False, False, False)) -> Tuple[
677681 for ext in ('.tsv' , '.tsv.gz' ): # NB: `ext` used to be '.json', which is more generic (but see https://github.com/bids-standard/bids-validator/issues/2113)
678682 if run_suffixok := bids_validator .BIDSValidator ().is_bids (f"/sub-unknown/{ datatype } /{ bidsname } { ext } " ): break # NB: Using the BIDSValidator sounds nice but doesn't give any control over the BIDS-version
679683 run_valsok = run_suffixok
680- LOGGER .bcdebug (f"bidsname={ run_suffixok } : /sub-unknown/{ datatype } /{ bidsname } .*" )
684+ LOGGER .bcdebug (f"bidsname (suffixok ={ run_suffixok } ) : /sub-unknown/{ datatype } /{ bidsname } .*" )
681685
682686 if checks [0 ] and run_keysok in (None , False ):
683687 LOGGER .bcdebug (f'Invalid "{ run_keysok } " key-checks in run-item: "{ bids ["suffix" ]} " ({ datatype } -> { provenance } )\n Run["bids"]:\t { bids } ' )
@@ -1100,8 +1104,8 @@ def __init__(self, yamlfile: Path, folder: Path=templatefolder, plugins: Iterabl
11001104 module = bcoin .import_plugin (plugin )
11011105 if not self .plugins .get (plugin ):
11021106 LOGGER .info (f"Adding default bidsmap options from the { plugin } plugin" )
1103- self .plugins [plugin ] = module .OPTIONS if 'OPTIONS' in dir ( module ) else {}
1104- if 'BIDSMAP' in dir ( module ) and yamlfile .parent == templatefolder :
1107+ self .plugins [plugin ] = module .OPTIONS if hasattr ( module , 'OPTIONS' ) else {}
1108+ if hasattr ( module , 'BIDSMAP' ) and yamlfile .parent == templatefolder :
11051109 for dataformat , datasection in module .BIDSMAP .items ():
11061110 if dataformat not in bidsmap_data :
11071111 LOGGER .info (f"Adding default bidsmappings from the { plugin } plugin" )
0 commit comments