1111from ...utils import verbose
1212from ..base import BaseRaw
1313
14+ _VOLT_SCALE = {"v" : 1.0 , "mv" : 1e-3 , "muv" : 1e-6 , "uv" : 1e-6 , "nv" : 1e-9 }
15+ _FREQ_SCALE = {"hz" : 1.0 , "khz" : 1e3 }
1416
15- def _parse_sampling_rate (val ):
16- # Accept e.g. "256", "256Hz", "256.0 Hz"
17- text = str (val ).strip ()
18- text = re .sub (r"\s*Hz\s*$" , "" , text , flags = re .IGNORECASE )
19- # Grab the first float-looking token
20- m = re .search (r"[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?" , text )
17+
18+ def _parse_value_with_unit (token , unit_scale = None ):
19+ """Split a numeric token with optional unit into value and scale."""
20+ text = str (token ).strip ().replace ("µ" , "u" )
21+ m = re .search (r"([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)\s*([a-zA-Z]*)" , text )
2122 if m is None :
22- raise ValueError (f"Could not parse SamplingRate from { val !r} " )
23- return float (m .group (0 ))
23+ raise ValueError (f"Could not parse numeric value from { token !r} " )
24+ num = float (m .group (1 ))
25+ unit = m .group (2 ).lower ()
26+ if unit_scale is None :
27+ scale = 1.0
28+ else :
29+ scale = unit_scale .get (unit , 1.0 )
30+ return num , scale
2431
2532
2633def _parse_bci2k_header (fname ):
@@ -73,8 +80,18 @@ def _parse_bci2k_header(fname):
7380 if "Parameter Definition" in current_section :
7481 if "=" in line :
7582 left , right = line .split ("=" , 1 )
76- name = left .strip ().split ()[- 1 ]
77- value = right .strip ().split ()[0 ]
83+ left_tokens = left .strip ().split ()
84+ name = left_tokens [- 1 ]
85+ param_type = left_tokens [- 2 ].lower () if len (left_tokens ) >= 2 else ""
86+ rhs = right .split ("//" , 1 )[0 ].strip ()
87+ rhs_tokens = rhs .split ()
88+ if not rhs_tokens :
89+ continue
90+ if param_type .endswith ("list" ):
91+ n_vals = int (rhs_tokens [0 ])
92+ value = rhs_tokens [1 : n_vals + 1 ]
93+ else :
94+ value = rhs_tokens [0 ]
7895 params [name ] = value
7996 continue
8097
@@ -101,7 +118,10 @@ def _parse_bci2k_header(fname):
101118 "Could not find 'SamplingRate' in the BCI2000 Parameter Definition section."
102119 )
103120
104- sfreq = _parse_sampling_rate (params ["SamplingRate" ])
121+ sfreq_val , sfreq_scale = _parse_value_with_unit (
122+ params ["SamplingRate" ], unit_scale = _FREQ_SCALE
123+ )
124+ sfreq = sfreq_val * sfreq_scale
105125
106126 return {
107127 "header_len" : header_len ,
@@ -154,6 +174,21 @@ def _read_bci2k_data(fname, info_dict):
154174 )
155175
156176 signal = signal .T .astype (np .float64 ) # (n_channels, n_samples)
177+ params = info_dict ["params" ]
178+ if "SourceChOffset" in params and "SourceChGain" in params :
179+ offsets = params ["SourceChOffset" ]
180+ gains = params ["SourceChGain" ]
181+ if len (offsets ) != n_channels or len (gains ) != n_channels :
182+ raise ValueError (
183+ "Expected SourceChOffset and SourceChGain lengths to match SourceCh."
184+ )
185+ offsets_arr = np .array ([_parse_value_with_unit (val )[0 ] for val in offsets ])
186+ gain_parsed = [_parse_value_with_unit (val , unit_scale = _VOLT_SCALE ) for val in gains ]
187+ gains_arr = np .array ([val for val , _ in gain_parsed ])
188+ gain_scales = np .array ([scale for _ , scale in gain_parsed ])
189+ signal = (signal + offsets_arr [:, np .newaxis ]) * (
190+ gains_arr [:, np .newaxis ] * gain_scales [:, np .newaxis ]
191+ )
157192 state_bytes = state_bytes .T # (state_vec_len, n_samples), dtype=uint8
158193
159194 return signal , state_bytes
0 commit comments