2626
2727def find_channel_directories (input_dir : str ) -> List [str ]:
2828 """
29- Find all subdirectories that contain E{channel}.txt files.
29+ Find all session subdirectories that contain E{channel}.txt files.
30+
31+ Looks for directories named session_* or session_{timestamp} containing
32+ channel data files from continuous recording sessions.
3033
3134 Args:
3235 input_dir: Parent directory to search
3336
3437 Returns:
35- List of subdirectory paths containing channel files
38+ List of session directory paths containing channel files, sorted chronologically
3639 """
3740 subdirs = []
3841
@@ -45,6 +48,7 @@ def find_channel_directories(input_dir: str) -> List[str]:
4548 if channel_files :
4649 subdirs .append (full_path )
4750
51+ # Sort by directory name (which includes timestamp for session directories)
4852 return sorted (subdirs )
4953
5054
@@ -74,15 +78,15 @@ def find_channel_files(directory: str) -> Dict[int, str]:
7478
7579def load_channel_data (
7680 channel_files : Dict [int , str ],
77- sample_rate : float ,
7881 interval_length : float = 1.0 ,
7982) -> Dict [int , List [Tuple [float , List [int ]]]]:
8083 """
81- Load data from all channel files.
84+ Load data from all channel files with per-channel sample rates.
85+
86+ Channel 0 uses 128 Hz (clock signal), all other channels use 512 Hz.
8287
8388 Args:
8489 channel_files: Dictionary mapping channel numbers to file paths
85- sample_rate: Sample rate in Hz
8690 interval_length: Length of each interval in seconds
8791
8892 Returns:
@@ -92,14 +96,21 @@ def load_channel_data(
9296
9397 for channel_num , file_path in channel_files .items ():
9498 try :
99+ # Auto-detect sample rate based on channel number
100+ # Channel 0: 128 Hz (clock signal)
101+ # Other channels: 512 Hz (default)
102+ sample_rate = 128.0 if channel_num == 0 else 512.0
103+
95104 intervals = TextSignalReader .read_signal (
96105 filepath = file_path ,
97106 sample_rate = sample_rate ,
98107 interval_length = interval_length ,
99108 )
100109 if intervals :
101110 channel_data [channel_num ] = intervals
102- print (f" Channel { channel_num } : { len (intervals )} intervals loaded" )
111+ print (
112+ f" Channel { channel_num } : { len (intervals )} intervals loaded ({ sample_rate } Hz)"
113+ )
103114 else :
104115 print (f" Channel { channel_num } : Warning - no data found" )
105116 except Exception as e :
@@ -115,10 +126,13 @@ def convert_directory(
115126 interval_length : float = 1.0 ,
116127) -> Optional [str ]:
117128 """
118- Convert all channel files in a directory to a single unified LabChart file.
129+ Convert all channel files in a session directory to a single unified LabChart file.
130+
131+ Extracts session timestamp from directory name (session_{timestamp}) for both
132+ creation date and output filename.
119133
120134 Args:
121- input_dir: Directory containing E{channel}.txt files
135+ input_dir: Session directory containing E{channel}.txt files
122136 output_dir: Output directory for LabChart file
123137 exporter: LabChartExporter instance
124138 interval_length: Length of each interval in seconds
@@ -138,21 +152,29 @@ def convert_directory(
138152
139153 print (f" Found { len (channel_files )} channel files: { sorted (channel_files .keys ())} " )
140154
141- # Load all channel data
155+ # Load all channel data with per-channel sample rates
142156 channel_data = load_channel_data (
143157 channel_files = channel_files ,
144- sample_rate = exporter .sample_rate ,
145158 interval_length = interval_length ,
146159 )
147160
148161 if not channel_data :
149162 print (f" Warning: No valid data loaded from any channels" )
150163 return None
151164
152- # Get creation date from directory metadata or first file
153- first_file = list (channel_files .values ())[0 ]
154- file_mtime = os .path .getmtime (first_file )
155- creation_date = datetime .fromtimestamp (file_mtime ).strftime ("%Y-%m-%d %H:%M:%S" )
165+ # Extract session timestamp from directory name (session_{timestamp})
166+ # Fallback to file modification time if extraction fails
167+ session_pattern = re .compile (r"session_(\d{10})" )
168+ match = session_pattern .match (dir_name )
169+
170+ if match :
171+ timestamp = int (match .group (1 ))
172+ creation_date = datetime .fromtimestamp (timestamp ).strftime ("%Y-%m-%d %H:%M:%S" )
173+ else :
174+ # Fallback to first file modification time
175+ first_file = list (channel_files .values ())[0 ]
176+ file_mtime = os .path .getmtime (first_file )
177+ creation_date = datetime .fromtimestamp (file_mtime ).strftime ("%Y-%m-%d %H:%M:%S" )
156178
157179 # Create output filename based on directory name
158180 output_file = os .path .join (output_dir , f"{ dir_name } .txt" )
@@ -184,12 +206,16 @@ def bulk_convert(
184206 glitch_threshold : int = 500 ,
185207) -> List [str ]:
186208 """
187- Bulk convert all channel directories to unified LabChart format.
209+ Bulk convert all session directories to unified LabChart format.
210+
211+ Sample rates are auto-detected per channel:
212+ - Channel 0: 128 Hz (clock signal)
213+ - Other channels: 512 Hz
188214
189215 Args:
190- input_dir: Input directory containing subdirectories with E{channel}.txt files
216+ input_dir: Input directory containing session subdirectories with E{channel}.txt files
191217 output_dir: Output directory (default: input_dir + '_labchart')
192- sample_rate: Sample rate in Hz
218+ sample_rate: DEPRECATED - Sample rates are now auto-detected per channel
193219 range_mV: Input dynamic range in millivolts
194220 interval_length: Length of each interval in seconds
195221 use_commas: Use European format (commas for decimals)
@@ -250,7 +276,9 @@ def bulk_convert(
250276 created_files .append (output_file )
251277
252278 print (f"\n Conversion complete!" )
253- print (f"Successfully converted { len (created_files )} out of { len (channel_dirs )} directories" )
279+ print (
280+ f"Successfully converted { len (created_files )} out of { len (channel_dirs )} directories"
281+ )
254282 print (f"Output files: { output_dir } " )
255283
256284 return created_files
@@ -259,38 +287,44 @@ def bulk_convert(
259287def main ():
260288 """Main entry point for command line usage"""
261289 parser = argparse .ArgumentParser (
262- description = "Bulk convert EEG channel directories to unified LabChart format" ,
290+ description = "Bulk convert EEG session directories to unified LabChart format" ,
263291 formatter_class = argparse .RawDescriptionHelpFormatter ,
264292 epilog = """
265293Examples:
266294 # Basic conversion with default settings
267- python bulk_converter.py ndf_files_text
295+ python bulk_converter.py test_sessions
268296
269- # Custom output directory and sample rate
270- python bulk_converter.py ndf_files_text output --sample-rate 1024 --range 30
297+ # Custom output directory with options
298+ python bulk_converter.py test_sessions output --range 120 --commas
271299
272300 # European format with microvolts
273- python bulk_converter.py data output --commas --microvolts
301+ python bulk_converter.py test_sessions output --commas --microvolts
274302
275- # High precision timing in milliseconds
276- python bulk_converter.py data output --milliseconds --absolute-time
303+ # High precision timing with absolute UNIX timestamps
304+ python bulk_converter.py test_sessions output --milliseconds --absolute-time
277305
278306Expected Input Structure:
279307 input_dir/
280- ├── M1555404530/
281- │ ├── E0.txt
282- │ ├── E1.txt
283- │ ├── E2.txt
284- │ └── E15.txt
285- └── M1555404531/
308+ ├── session_1555404530/ (continuous recording session)
309+ │ ├── E0.txt (Channel 0: 128 Hz clock signal)
310+ │ ├── E1.txt (Channel 1: 512 Hz)
311+ │ ├── E2.txt (Channel 2: 512 Hz)
312+ │ └── E15.txt (Channel 15: 512 Hz)
313+ └── session_1558948567/ (next session after gap)
286314 ├── E0.txt
287315 ├── E1.txt
288316 └── E2.txt
289317
290318Output:
291319 output_dir/
292- ├── M1555404530.txt (contains all channels in unified format)
293- └── M1555404531.txt (contains all channels in unified format)
320+ ├── session_1555404530.txt (all channels merged, tab-separated)
321+ └── session_1558948567.txt (all channels merged, tab-separated)
322+
323+ Notes:
324+ - Channel 0 is automatically detected as 128 Hz (clock signal)
325+ - All other channels are automatically detected as 512 Hz
326+ - The --sample-rate option is deprecated but retained for compatibility
327+ - Session directories are typically created by ndf_to_text_converter.py
294328 """ ,
295329 )
296330
@@ -310,7 +344,7 @@ def main():
310344 "-sr" ,
311345 type = float ,
312346 default = 512.0 ,
313- help = "Sample rate in Hz (default: 512 )" ,
347+ help = "DEPRECATED - Sample rates are auto-detected per channel (Ch0: 128Hz, others: 512Hz )" ,
314348 )
315349
316350 parser .add_argument (
0 commit comments