@@ -61,15 +61,16 @@ def __init__(self, dirname='', experiment_names=None):
6161 if isinstance (experiment_names , str ):
6262 experiment_names = [experiment_names ]
6363 self .experiment_names = experiment_names
64+ self .folder_structure = None
6465
6566 def _source_name (self ):
6667 return self .dirname
6768
6869 def _parse_header (self ):
69- all_streams , nb_block , nb_segment_per_block , possible_experiments = \
70+ folder_structure , all_streams , nb_block , nb_segment_per_block , possible_experiments = \
7071 explore_folder (self .dirname , self .experiment_names )
71- check_stream_consistency ( all_streams , nb_block , nb_segment_per_block ,
72- possible_experiments )
72+ check_folder_consistency ( folder_structure , possible_experiments )
73+ self . folder_structure = folder_structure
7374
7475 # all streams are consistent across blocks and segments
7576 sig_stream_names = sorted (list (all_streams [0 ][0 ]['continuous' ].keys ()))
@@ -354,106 +355,137 @@ def explore_folder(dirname, experiment_names=None):
354355 nb_segment_per_block = {}
355356 # nested dictionary: block_index > seg_index > data_type > stream_name
356357 all_streams = {}
357- block_names_dict = {}
358358 possible_experiment_names = []
359+
360+ # folder with nodes, experiments, setting files, recordings, and streams
361+ folder_structure = {}
362+
359363 for root , dirs , files in os .walk (dirname ):
360364 for file in files :
361365 if not file == 'structure.oebin' :
362366 continue
363367 root = Path (root )
364368
365- node_name = root .parents [1 ].stem
369+ node_folder = root .parents [1 ]
370+ node_name = node_folder .stem
366371 if not node_name .startswith ('Record' ):
367372 # before version 5.x.x there was not multi Node recording
368373 # so no node_name
369374 node_name = ''
370375
376+ if node_name not in folder_structure :
377+ folder_structure [node_name ] = {}
378+ folder_structure [node_name ]['experiments' ] = []
379+
371380 # here we skip if experiment_names is not None
372- experiment_name = root .parents [0 ].stem
381+ experiment_folder = root .parents [0 ]
382+ experiment_name = experiment_folder .stem
373383 possible_experiment_names .append (experiment_name )
374384 if experiment_names is not None and experiment_name not in experiment_names :
375385 continue
376- block_name = experiment_name
377- if block_name not in block_names_dict :
378- block_index = nb_block
379- segment_names_dict = {}
380- block_names_dict [experiment_name ] = block_index
381- all_streams [block_index ] = {}
382- nb_segment_per_block [block_index ] = 0
383- nb_block += 1
384- seg_index = - 1
386+ if experiment_name not in [e ['name' ] for e in folder_structure [node_name ]['experiments' ]]:
387+ experiment = {}
388+ experiment ['name' ] = experiment_name
389+ if experiment_name == 'experiment1' :
390+ settings_file = node_folder / "settings.xml"
391+ else :
392+ settings_file = node_folder / f"settings_{ experiment_folder .stem .replace ('experiment' , '' )} .xml"
393+ experiment ['settings' ] = settings_file
394+ experiment ['recordings' ] = []
395+ folder_structure [node_name ]['experiments' ].append (experiment )
385396
397+ recording_folder = root
386398 recording_name = root .stem
387- if recording_name not in segment_names_dict :
388- # segment index restarts from zero for each block (index)
389- seg_index += 1
390- all_streams [block_index ][seg_index ] = {
391- 'continuous' : {},
392- 'events' : {},
393- 'spikes' : {},
394- }
395- segment_names_dict [recording_name ] = seg_index
396- nb_segment_per_block [block_index ] += 1
397-
398- # metadata
399- with open (root / 'structure.oebin' , encoding = 'utf8' , mode = 'r' ) as f :
400- structure = json .load (f )
401-
402- if (root / 'continuous' ).exists () and len (structure ['continuous' ]) > 0 :
403- for d in structure ['continuous' ]:
404- # when multi Record Node the stream name also contains
405- # the node name to make it unique
406- stream_name = node_name + '#' + d ['folder_name' ]
407-
408- raw_filename = root / 'continuous' / d ['folder_name' ] / 'continuous.dat'
409-
410- # Updates for OpenEphys v0.6:
411- # In new vesion (>=0.6) timestamps.npy is now called sample_numbers.npy
412- # see https://open-ephys.github.io/gui-docs/User-Manual/Recording-data/Binary-format.html#continuous
413- if (root / 'continuous' / d ['folder_name' ] / 'sample_numbers.npy' ).is_file ():
414- timestamp_file = root / 'continuous' / d ['folder_name' ] / \
415- 'sample_numbers.npy'
416- else :
417- timestamp_file = root / 'continuous' / d ['folder_name' ] / 'timestamps.npy'
418- timestamps = np .load (str (timestamp_file ), mmap_mode = 'r' )
419- timestamp0 = timestamps [0 ]
420- t_start = timestamp0 / d ['sample_rate' ]
421-
422- # TODO for later : gap checking
423- signal_stream = d .copy ()
424- signal_stream ['raw_filename' ] = str (raw_filename )
425- signal_stream ['dtype' ] = 'int16'
426- signal_stream ['timestamp0' ] = timestamp0
427- signal_stream ['t_start' ] = t_start
428-
429- all_streams [block_index ][seg_index ]['continuous' ][stream_name ] = signal_stream
430-
431- if (root / 'events' ).exists () and len (structure ['events' ]) > 0 :
432- for d in structure ['events' ]:
433- stream_name = node_name + '#' + d ['folder_name' ]
434-
435- event_stream = d .copy ()
436- for name in _possible_event_stream_names :
437- npz_filename = root / 'events' / d ['folder_name' ] / f'{ name } .npy'
438- if npz_filename .is_file ():
439- event_stream [f'{ name } _npy' ] = str (npz_filename )
440-
441- all_streams [block_index ][seg_index ]['events' ][stream_name ] = event_stream
442-
443- possible_experiment_names = list (np .unique (possible_experiment_names ))
444-
445- return all_streams , nb_block , nb_segment_per_block , possible_experiment_names
446-
447-
448- def check_stream_consistency (all_streams , nb_block , nb_segment_per_block ,
449- possible_experiment_names = None ):
399+ if recording_name not in [r ['name' ] for r in folder_structure [node_name ]['experiments' ][- 1 ]['recordings' ]]:
400+ recording = {}
401+ recording ['name' ] = recording_name
402+ recording ['streams' ] = {}
403+
404+ # metadata
405+ with open (recording_folder / 'structure.oebin' , encoding = 'utf8' , mode = 'r' ) as f :
406+ rec_structure = json .load (f )
407+
408+ if (recording_folder / 'continuous' ).exists () and len (rec_structure ['continuous' ]) > 0 :
409+ recording ['streams' ]['continuous' ] = {}
410+ for d in rec_structure ['continuous' ]:
411+ # when multi Record Node the stream name also contains
412+ # the node name to make it unique
413+ oe_stream_name = Path (d ["folder_name" ]).name # remove trailing slash
414+ stream_name = node_name + '#' + oe_stream_name
415+ raw_filename = recording_folder / 'continuous' / d ['folder_name' ] / 'continuous.dat'
416+
417+ # Updates for OpenEphys v0.6:
418+ # In new vesion (>=0.6) timestamps.npy is now called sample_numbers.npy
419+ # see https://open-ephys.github.io/gui-docs/User-Manual/Recording-data/Binary-format.html#continuous
420+ if (recording_folder / 'continuous' / d ['folder_name' ] / 'sample_numbers.npy' ).is_file ():
421+ timestamp_file = recording_folder / 'continuous' / d ['folder_name' ] / \
422+ 'sample_numbers.npy'
423+ else :
424+ timestamp_file = recording_folder / 'continuous' / d ['folder_name' ] / 'timestamps.npy'
425+ timestamps = np .load (str (timestamp_file ), mmap_mode = 'r' )
426+ timestamp0 = timestamps [0 ]
427+ t_start = timestamp0 / d ['sample_rate' ]
428+
429+ # TODO for later : gap checking
430+ signal_stream = d .copy ()
431+ signal_stream ['raw_filename' ] = str (raw_filename )
432+ signal_stream ['dtype' ] = 'int16'
433+ signal_stream ['timestamp0' ] = timestamp0
434+ signal_stream ['t_start' ] = t_start
435+
436+ recording ['streams' ]['continuous' ][stream_name ] = signal_stream
437+
438+ if (root / 'events' ).exists () and len (rec_structure ['events' ]) > 0 :
439+ recording ['streams' ]['events' ] = {}
440+ for d in rec_structure ['events' ]:
441+ oe_stream_name = Path (d ["folder_name" ]).name # remove trailing slash
442+ stream_name = node_name + '#' + oe_stream_name
443+
444+ event_stream = d .copy ()
445+ for name in _possible_event_stream_names :
446+ npy_filename = root / 'events' / d ['folder_name' ] / f'{ name } .npy'
447+ if npy_filename .is_file ():
448+ event_stream [f'{ name } _npy' ] = str (npy_filename )
449+
450+ recording ['streams' ]['events' ][stream_name ] = event_stream
451+
452+ folder_structure [node_name ]['experiments' ][- 1 ]['recordings' ].append (recording )
453+
454+ # now create all_streams, nb_block, nb_segment_per_block (from first recording Node)
455+ recording_node = folder_structure [list (folder_structure .keys ())[0 ]]
456+ nb_block = len (recording_node ['experiments' ])
457+ for block_index , experiment in enumerate (recording_node ['experiments' ]):
458+ nb_segment_per_block [block_index ] = len (experiment ['recordings' ])
459+ all_streams [block_index ] = {}
460+ for seg_index , recording in enumerate (experiment ['recordings' ]):
461+ all_streams [block_index ][seg_index ] = {}
462+ for stream_type in recording ['streams' ]:
463+ all_streams [block_index ][seg_index ][stream_type ] = {}
464+ for stream_name , signal_stream in recording ['streams' ][stream_type ].items ():
465+ all_streams [block_index ][seg_index ][stream_type ][stream_name ] = signal_stream
466+
467+ return folder_structure , all_streams , nb_block , nb_segment_per_block , possible_experiment_names
468+
469+
470+ def check_folder_consistency (folder_structure , possible_experiment_names = None ):
471+ # experiments across nodes
472+ if len (folder_structure ) > 1 :
473+ experiments = None
474+ for node in folder_structure .values ():
475+ experiments_node = node ['experiments' ]
476+ if experiments is None :
477+ experiments = experiments_node
478+ experiment_names = [e ['name' ] for e in experiments ]
479+ assert all (ename ['name' ] in experiment_names for ename in experiments_node ), \
480+ ("Inconsistent experiments across recording nodes!" )
481+
450482 # "continuous" streams across segments
451- for block_index in range (nb_block ):
483+ experiments = folder_structure [list (folder_structure .keys ())[0 ]]['experiments' ]
484+ for experiment in experiments :
452485 segment_stream_names = None
453- if nb_segment_per_block [block_index ] > 1 :
454- for segment_index in all_streams [block_index ]:
455- stream_names = sorted (list (all_streams [block_index ]
456- [segment_index ]["continuous" ].keys ()))
486+ if len (experiment ['recordings' ]) > 1 :
487+ for recording in experiment ['recordings' ]:
488+ stream_names = sorted (list (recording ['streams' ].keys ()))
457489 if segment_stream_names is None :
458490 segment_stream_names = stream_names
459491 assert segment_stream_names == stream_names , \
@@ -463,12 +495,13 @@ def check_stream_consistency(all_streams, nb_block, nb_segment_per_block,
463495
464496 # "continuous" streams across blocks
465497 block_stream_names = None
466- for block_index in range (nb_block ):
467- # use 1st segment
468- stream_names = sorted (list (all_streams [block_index ][0 ]["continuous" ].keys ()))
469- if block_stream_names is None :
470- block_stream_names = stream_names
471- assert block_stream_names == stream_names , \
472- (f"Inconsistent continuous streams across blocks (experiments)! Streams for "
473- f"different experiments in the same folder must be the same. You can load a subset "
474- f"of experiments with the 'experiment_names' argument: { possible_experiment_names } " )
498+ if len (experiments ) > 1 :
499+ for experiment in experiments :
500+ # use 1st segment
501+ stream_names = sorted (list (experiment ['recordings' ][0 ]['streams' ].keys ()))
502+ if block_stream_names is None :
503+ block_stream_names = stream_names
504+ assert block_stream_names == stream_names , \
505+ (f"Inconsistent continuous streams across blocks (experiments)! Streams for "
506+ f"different experiments in the same folder must be the same. You can load a subset "
507+ f"of experiments with the 'experiment_names' argument: { possible_experiment_names } " )
0 commit comments