Skip to content

Commit ffec226

Browse files
committed
better variable name
1 parent 1a1368b commit ffec226

File tree

1 file changed

+180
-34
lines changed

1 file changed

+180
-34
lines changed

neo/rawio/openephysbinaryrawio.py

Lines changed: 180 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,18 @@ def __init__(self, dirname="", load_sync_channel=False, experiment_names=None):
130130
def _source_name(self):
131131
return self.dirname
132132

133-
134133
def _parse_header(self):
135134
# Use the static private methods directly
136-
folder_structure_dict, possible_experiments = self._parse_folder_structure(self.dirname, self.experiment_names)
137-
135+
folder_structure_dict, possible_experiments = OpenEphysBinaryRawIO._parse_folder_structure(
136+
self.dirname, self.experiment_names
137+
)
138138
check_folder_consistency(folder_structure_dict, possible_experiments)
139139
self.folder_structure = folder_structure_dict
140140

141141
# Map folder structure to Neo indexing
142-
all_streams, nb_block, nb_segment_per_block = self._map_folder_structure_to_neo(folder_structure_dict)
142+
all_streams, nb_block, nb_segment_per_block = OpenEphysBinaryRawIO._map_folder_structure_to_neo(
143+
open_ephys_folder_structure_dict=folder_structure_dict
144+
)
143145

144146
# all streams are consistent across blocks and segments.
145147
# also checks that 'continuous' and 'events' folder are present
@@ -690,22 +692,91 @@ def _get_analogsignal_buffer_description(self, block_index, seg_index, buffer_id
690692
def _parse_folder_structure(dirname, experiment_names=None):
691693
"""
692694
Parse the OpenEphys folder structure by scanning for recordings.
693-
694-
This is a private method that handles the core folder discovery logic.
695-
695+
696+
This method walks through the directory tree looking for structure.oebin files,
697+
then builds a hierarchical dictionary that mirrors the OpenEphys folder organization.
698+
It extracts metadata from each structure.oebin file and organizes it by hardware
699+
nodes, experiments, and recordings.
700+
696701
Parameters
697702
----------
698703
dirname : str
699704
Root folder of the OpenEphys dataset
700705
experiment_names : str, list, or None
701706
If multiple experiments are available, select specific experiments.
702-
707+
If None, all experiments are discovered and included.
708+
703709
Returns
704710
-------
705-
folder_structure : dict
711+
open_ephys_folder_structure_dict : dict
706712
Hierarchical dictionary describing the raw OpenEphys folder structure.
707-
possible_experiment_names : list
708-
List of all available experiment names found in the folder
713+
Structure: [node_name]["experiments"][exp_id]["recordings"][rec_id]["streams"][stream_type][stream_name] -> <parsed_oebin_info>
714+
715+
Where:
716+
- node_name: str, e.g., "Record Node 102" or "" for single-node recordings
717+
- exp_id: int, experiment number from folder name (1, 2, 3, ...)
718+
- rec_id: int, recording number from folder name (1, 2, 3, ...)
719+
- stream_type: str, either "continuous" or "events"
720+
- stream_name: str, e.g., "AP_band", "LF_band", "TTL_1"
721+
- <stream_metadata>: dict containing stream-specific metadata from structure.oebin plus added file paths
722+
723+
For continuous streams, includes:
724+
{
725+
"channels": [{"channel_name": "CH1", "bit_volts": 0.195, ...}, ...],
726+
"sample_rate": 30000.0,
727+
"raw_filename": "/path/to/continuous.dat",
728+
"dtype": "int16",
729+
"timestamp0": 123456,
730+
"t_start": 0.0
731+
}
732+
733+
For event streams, includes:
734+
{
735+
"channel_name": "TTL_1",
736+
"timestamps_npy": "/path/to/timestamps.npy",
737+
"channels_npy": "/path/to/channels.npy",
738+
"sample_numbers_npy": "/path/to/sample_numbers.npy"
739+
}
740+
741+
possible_experiment_names : list of str
742+
List of all experiment folder names found, naturally sorted (e.g., ["experiment1", "experiment2"])
743+
744+
Examples
745+
--------
746+
For a typical multi-node Neuropixels recording:
747+
748+
```python
749+
open_ephys_folder_structure_dict, experiments = _parse_folder_structure("/path/to/data")
750+
751+
# Structure shape:
752+
open_ephys_folder_structure_dict = {
753+
"Record Node 102": {
754+
"experiments": {
755+
1: { # from "experiment1" folder
756+
"name": "experiment1",
757+
"settings_file": Path("/path/settings.xml"),
758+
"recordings": {
759+
1: { # from "recording1" folder
760+
"name": "recording1",
761+
"streams": {
762+
"continuous": {
763+
"AP_band": <continuous_stream_metadata>,
764+
"LF_band": <continuous_stream_metadata>
765+
},
766+
"events": {
767+
"TTL_1": <event_stream_metadata>
768+
}
769+
}
770+
}
771+
}
772+
}
773+
}
774+
},
775+
"Record Node 103": { ... } # Same structure for additional nodes
776+
}
777+
778+
experiments = ["experiment1", "experiment2"]
779+
```
709780
"""
710781
folder_structure = {}
711782
possible_experiment_names = []
@@ -846,28 +917,101 @@ def _parse_folder_structure(dirname, experiment_names=None):
846917
def _map_folder_structure_to_neo(open_ephys_folder_structure_dict):
847918
"""
848919
Map folder structure to Neo indexing system.
849-
850-
Converts the hierarchical folder structure to a flattened dictionary
851-
organized by Neo's block_index (experiments) and seg_index (recordings).
852-
920+
921+
This method transforms OpenEphys's native hierarchical organization into Neo's
922+
flattened block/segment indexing system. OpenEphys organizes data by hardware
923+
nodes, experiments, and recordings, while Neo uses a two-level structure
924+
of blocks and segments with numerical indices.
925+
853926
Parameters
854927
----------
855928
open_ephys_folder_structure_dict : dict
856929
Hierarchical folder structure from _parse_folder_structure()
857-
930+
Structure: [node_name]["experiments"][exp_id]["recordings"][rec_id][stream_type][stream_name]
931+
858932
Returns
859933
-------
860-
all_streams : dict
934+
block_segment_streams_dict : dict
861935
Neo-indexed dictionary: [block_index][seg_index][stream_type][stream_name]
936+
Where block_index maps to experiment numbers and seg_index maps to recording numbers
862937
nb_block : int
863-
Number of blocks (experiments)
938+
Total number of blocks (experiments) available
864939
nb_segment_per_block : dict
865-
Number of segments per block. Keys are block indices.
940+
Number of segments per block. Keys are block indices, values are segment counts
941+
942+
Examples
943+
--------
944+
**Input OpenEphys folder_structure_dict:**
945+
946+
OpenEphys native organization with hardware nodes, experiments, and recordings:
947+
948+
```python
949+
{
950+
"Record Node 102": { # Hardware device 1
951+
"experiments": {
952+
1: { # experiment1 folder
953+
"recordings": {
954+
1: {"streams": {"continuous": {"AP_band": <continuous_stream_metadata>, "LF_band": <continuous_stream_metadata>}}}, # recording1
955+
2: {"streams": {"continuous": {"AP_band": <continuous_stream_metadata>, "LF_band": <continuous_stream_metadata>}}} # recording2
956+
}
957+
},
958+
2: { # experiment2 folder
959+
"recordings": {
960+
1: {"streams": {"continuous": {"AP_band": <continuous_stream_metadata>, "LF_band": <continuous_stream_metadata>}}} # recording1
961+
}
962+
}
963+
}
964+
},
965+
"Record Node 103": { # Hardware device 2 (same structure)
966+
"experiments": {
967+
1: {"recordings": {1: {"streams": {"continuous": {"AP_band": <continuous_stream_metadata>}}}}},
968+
2: {"recordings": {1: {"streams": {"continuous": {"AP_band": <continuous_stream_metadata>}}}}}
969+
}
970+
}
971+
}
972+
```
973+
974+
**Output Neo block_segment_streams_dict:**
975+
976+
Neo's flattened block/segment organization with merged multi-node streams:
977+
978+
```python
979+
{
980+
0: { # block_index 0 (experiment1)
981+
0: { # seg_index 0 (recording1)
982+
"continuous": {
983+
"Record Node 102#AP_band": <continuous_stream_metadata>, # Node prefix added
984+
"Record Node 102#LF_band": <continuous_stream_metadata>,
985+
"Record Node 103#AP_band": <continuous_stream_metadata> # Streams from both nodes merged
986+
}
987+
},
988+
1: { # seg_index 1 (recording2) - only exists for Record Node 102
989+
"continuous": {
990+
"Record Node 102#AP_band": <continuous_stream_metadata>,
991+
"Record Node 102#LF_band": <continuous_stream_metadata>
992+
}
993+
}
994+
},
995+
1: { # block_index 1 (experiment2)
996+
0: { # seg_index 0 (recording1)
997+
"continuous": {
998+
"Record Node 102#AP_band": <continuous_stream_metadata>,
999+
"Record Node 102#LF_band": <continuous_stream_metadata>,
1000+
"Record Node 103#AP_band": <continuous_stream_metadata>
1001+
}
1002+
}
1003+
}
1004+
}
1005+
```
1006+
1007+
**Other outputs:**
1008+
- `nb_block`: 2 (two experiments total)
1009+
- `nb_segment_per_block`: {0: 2, 1: 1} (experiment1 has 2 recordings, experiment2 has 1)
8661010
"""
867-
all_streams = {}
1011+
block_segment_streams_dict = {}
8681012
nb_segment_per_block = {}
8691013
record_node_names = list(open_ephys_folder_structure_dict.keys())
870-
1014+
8711015
# Use first record node to determine number of blocks
8721016
recording_node = open_ephys_folder_structure_dict[record_node_names[0]]
8731017
nb_block = len(recording_node["experiments"])
@@ -878,21 +1022,21 @@ def _map_folder_structure_to_neo(open_ephys_folder_structure_dict):
8781022
for block_index, _ in enumerate(exp_ids_sorted):
8791023
experiment = recording_node["experiments"][exp_ids_sorted[block_index]]
8801024
nb_segment_per_block[block_index] = len(experiment["recordings"])
881-
if block_index not in all_streams:
882-
all_streams[block_index] = {}
1025+
if block_index not in block_segment_streams_dict:
1026+
block_segment_streams_dict[block_index] = {}
8831027

8841028
rec_ids_sorted = sorted(list(experiment["recordings"].keys()))
8851029
for seg_index, _ in enumerate(rec_ids_sorted):
8861030
recording = experiment["recordings"][rec_ids_sorted[seg_index]]
887-
if seg_index not in all_streams[block_index]:
888-
all_streams[block_index][seg_index] = {}
1031+
if seg_index not in block_segment_streams_dict[block_index]:
1032+
block_segment_streams_dict[block_index][seg_index] = {}
8891033
for stream_type in recording["streams"]:
890-
if stream_type not in all_streams[block_index][seg_index]:
891-
all_streams[block_index][seg_index][stream_type] = {}
1034+
if stream_type not in block_segment_streams_dict[block_index][seg_index]:
1035+
block_segment_streams_dict[block_index][seg_index][stream_type] = {}
8921036
for stream_name, signal_stream in recording["streams"][stream_type].items():
893-
all_streams[block_index][seg_index][stream_type][stream_name] = signal_stream
1037+
block_segment_streams_dict[block_index][seg_index][stream_type][stream_name] = signal_stream
8941038

895-
return all_streams, nb_block, nb_segment_per_block
1039+
return block_segment_streams_dict, nb_block, nb_segment_per_block
8961040

8971041

8981042
_possible_event_stream_names = (
@@ -943,7 +1087,9 @@ def explore_folder(dirname, experiment_names=None):
9431087
List of all available experiments in the Open Ephys folder
9441088
"""
9451089
# Use the static private methods for the implementation
946-
folder_structure, possible_experiment_names = OpenEphysBinaryRawIO._parse_folder_structure(dirname, experiment_names)
1090+
folder_structure, possible_experiment_names = OpenEphysBinaryRawIO._parse_folder_structure(
1091+
dirname, experiment_names
1092+
)
9471093
all_streams, nb_block, nb_segment_per_block = OpenEphysBinaryRawIO._map_folder_structure_to_neo(folder_structure)
9481094

9491095
return folder_structure, all_streams, nb_block, nb_segment_per_block, possible_experiment_names
@@ -952,17 +1098,17 @@ def explore_folder(dirname, experiment_names=None):
9521098
def check_folder_consistency(folder_structure, possible_experiment_names=None):
9531099
"""
9541100
Validate consistency of the discovered OpenEphys folder structure.
955-
956-
Ensures that multi-node recordings have consistent experiment/recording
1101+
1102+
Ensures that multi-node recordings have consistent experiment/recording
9571103
structures and that streams are consistent across segments and blocks.
958-
1104+
9591105
Parameters
9601106
----------
9611107
folder_structure : dict
9621108
Folder structure from explore_folder()
9631109
possible_experiment_names : list, optional
9641110
Available experiment names for error messages
965-
1111+
9661112
Raises
9671113
------
9681114
ValueError

0 commit comments

Comments
 (0)