Skip to content

Commit 8be2db7

Browse files
authored
Merge pull request #1797 from Kkuntal990/BrainVisionIO-vectorized-fix
Add support for VECTORIZED orientation in BrainVisionRawIO
2 parents 97bd49d + 7bf5208 commit 8be2db7

File tree

2 files changed

+77
-26
lines changed

2 files changed

+77
-26
lines changed

neo/rawio/baserawio.py

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,9 @@ def __init__(self, *arg, **kwargs):
15771577
def _get_signal_size(self, block_index, seg_index, stream_index):
15781578
buffer_id = self.header["signal_streams"][stream_index]["buffer_id"]
15791579
buffer_desc = self.get_analogsignal_buffer_description(block_index, seg_index, buffer_id)
1580-
# some hdf5 revert teh buffer
1580+
# time_axis indicates which dimension is time:
1581+
# time_axis=0: shape is (time, channels)
1582+
# time_axis=1: shape is (channels, time)
15811583
time_axis = buffer_desc.get("time_axis", 0)
15821584
return buffer_desc["shape"][time_axis]
15831585

@@ -1598,34 +1600,67 @@ def _get_analogsignal_chunk(
15981600

15991601
buffer_desc = self.get_analogsignal_buffer_description(block_index, seg_index, buffer_id)
16001602

1603+
# Get time_axis to determine which dimension is time
1604+
time_axis = buffer_desc.get("time_axis", 0)
1605+
16011606
i_start = i_start or 0
1602-
i_stop = i_stop or buffer_desc["shape"][0]
1607+
i_stop = i_stop or buffer_desc["shape"][time_axis]
16031608

16041609
if buffer_desc["type"] == "raw":
16051610

1606-
# open files on demand and keep reference to opened file
1607-
if not hasattr(self, "_memmap_analogsignal_buffers"):
1608-
self._memmap_analogsignal_buffers = {}
1609-
if block_index not in self._memmap_analogsignal_buffers:
1610-
self._memmap_analogsignal_buffers[block_index] = {}
1611-
if seg_index not in self._memmap_analogsignal_buffers[block_index]:
1612-
self._memmap_analogsignal_buffers[block_index][seg_index] = {}
1613-
if buffer_id not in self._memmap_analogsignal_buffers[block_index][seg_index]:
1614-
fid = open(buffer_desc["file_path"], mode="rb")
1615-
self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id] = fid
1616-
else:
1617-
fid = self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id]
1611+
if time_axis == 0:
1612+
# MULTIPLEXED: time_axis=0 means (time, channels) layout
1613+
# open files on demand and keep reference to opened file
1614+
if not hasattr(self, "_memmap_analogsignal_buffers"):
1615+
self._memmap_analogsignal_buffers = {}
1616+
if block_index not in self._memmap_analogsignal_buffers:
1617+
self._memmap_analogsignal_buffers[block_index] = {}
1618+
if seg_index not in self._memmap_analogsignal_buffers[block_index]:
1619+
self._memmap_analogsignal_buffers[block_index][seg_index] = {}
1620+
if buffer_id not in self._memmap_analogsignal_buffers[block_index][seg_index]:
1621+
fid = open(buffer_desc["file_path"], mode="rb")
1622+
self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id] = fid
1623+
else:
1624+
fid = self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id]
1625+
1626+
num_channels = buffer_desc["shape"][1]
1627+
1628+
raw_sigs = get_memmap_chunk_from_opened_file(
1629+
fid,
1630+
num_channels,
1631+
i_start,
1632+
i_stop,
1633+
np.dtype(buffer_desc["dtype"]),
1634+
file_offset=buffer_desc["file_offset"],
1635+
)
16181636

1619-
num_channels = buffer_desc["shape"][1]
1637+
elif time_axis == 1:
1638+
# VECTORIZED: time_axis=1 means shape is (channels, time)
1639+
# Data is stored as [all_samples_ch1, all_samples_ch2, ...]
1640+
dtype = np.dtype(buffer_desc["dtype"])
1641+
num_channels = buffer_desc["shape"][0] # shape is (channels, time)
1642+
num_samples = i_stop - i_start
1643+
total_samples_per_channel = buffer_desc["shape"][1] # shape is (channels, time)
1644+
1645+
# Determine which channels to read
1646+
if channel_indexes is None:
1647+
chan_indices = np.arange(num_channels)
1648+
else:
1649+
chan_indices = np.asarray(channel_indexes)
1650+
1651+
raw_sigs = np.empty((num_samples, len(chan_indices)), dtype=dtype)
1652+
1653+
for i, chan_idx in enumerate(chan_indices):
1654+
offset = buffer_desc["file_offset"] + chan_idx * total_samples_per_channel * dtype.itemsize
1655+
channel_data = np.memmap(buffer_desc["file_path"], dtype=dtype, mode='r',
1656+
offset=offset, shape=(total_samples_per_channel,))
1657+
raw_sigs[:, i] = channel_data[i_start:i_stop]
1658+
1659+
# Channel slicing already done above, so skip later channel_indexes slicing
1660+
channel_indexes = None
16201661

1621-
raw_sigs = get_memmap_chunk_from_opened_file(
1622-
fid,
1623-
num_channels,
1624-
i_start,
1625-
i_stop,
1626-
np.dtype(buffer_desc["dtype"]),
1627-
file_offset=buffer_desc["file_offset"],
1628-
)
1662+
else:
1663+
raise ValueError(f"time_axis must be 0 or 1, got {time_axis}")
16291664

16301665
elif buffer_desc["type"] == "hdf5":
16311666

neo/rawio/brainvisionrawio.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ def _parse_header(self):
6262
raise NeoReadWriteError(
6363
f"Only `BINARY` format has been implemented. Current Data Format is {vhdr_header['Common Infos']['DataFormat']}"
6464
)
65-
if vhdr_header["Common Infos"]["DataOrientation"] != "MULTIPLEXED":
65+
66+
# Store the data orientation for later use in reading
67+
self._data_orientation = vhdr_header["Common Infos"]["DataOrientation"]
68+
if self._data_orientation not in ("MULTIPLEXED", "VECTORIZED"):
6669
raise NeoReadWriteError(
67-
f"Only `MULTIPLEXED` is implemented. Current Orientation is {vhdr_header['Common Infos']['DataOrientation']}"
70+
f"Data orientation must be either `MULTIPLEXED` or `VECTORIZED`. Current Orientation is {self._data_orientation}"
6871
)
6972

7073
nb_channel = int(vhdr_header["Common Infos"]["NumberOfChannels"])
@@ -87,14 +90,27 @@ def _parse_header(self):
8790
buffer_id = "0"
8891
self._buffer_descriptions = {0: {0: {}}}
8992
self._stream_buffer_slice = {}
90-
shape = get_memmap_shape(binary_filename, sig_dtype, num_channels=nb_channel, offset=0)
93+
94+
# time_axis indicates data layout: 0 for MULTIPLEXED (time, channels), 1 for VECTORIZED (channels, time)
95+
time_axis = 0 if self._data_orientation == "MULTIPLEXED" else 1
96+
97+
# Get shape - always returns (num_samples, num_channels)
98+
temp_shape = get_memmap_shape(binary_filename, sig_dtype, num_channels=nb_channel, offset=0)
99+
100+
# For consistency with HDF5 pattern: when time_axis=1, shape should be (channels, time)
101+
if time_axis == 1:
102+
shape = (temp_shape[1], temp_shape[0]) # (num_channels, num_samples)
103+
else:
104+
shape = temp_shape # (num_samples, num_channels)
105+
91106
self._buffer_descriptions[0][0][buffer_id] = {
92107
"type": "raw",
93108
"file_path": binary_filename,
94109
"dtype": str(sig_dtype),
95110
"order": "C",
96111
"file_offset": 0,
97112
"shape": shape,
113+
"time_axis": time_axis,
98114
}
99115
self._stream_buffer_slice[stream_id] = None
100116

0 commit comments

Comments
 (0)