Skip to content

Commit 3d8c702

Browse files
committed
Imrove explore folder: read folders first and map block and seg index after
1 parent 9c0ceae commit 3d8c702

File tree

1 file changed

+125
-92
lines changed

1 file changed

+125
-92
lines changed

neo/rawio/openephysbinaryrawio.py

Lines changed: 125 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)