Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 58 additions & 23 deletions neo/rawio/baserawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -1577,7 +1577,9 @@ def __init__(self, *arg, **kwargs):
def _get_signal_size(self, block_index, seg_index, stream_index):
buffer_id = self.header["signal_streams"][stream_index]["buffer_id"]
buffer_desc = self.get_analogsignal_buffer_description(block_index, seg_index, buffer_id)
# some hdf5 revert teh buffer
# time_axis indicates which dimension is time:
# time_axis=0: shape is (time, channels)
# time_axis=1: shape is (channels, time)
time_axis = buffer_desc.get("time_axis", 0)
return buffer_desc["shape"][time_axis]

Expand All @@ -1598,34 +1600,67 @@ def _get_analogsignal_chunk(

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

# Get time_axis to determine which dimension is time
time_axis = buffer_desc.get("time_axis", 0)

i_start = i_start or 0
i_stop = i_stop or buffer_desc["shape"][0]
i_stop = i_stop or buffer_desc["shape"][time_axis]

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

# open files on demand and keep reference to opened file
if not hasattr(self, "_memmap_analogsignal_buffers"):
self._memmap_analogsignal_buffers = {}
if block_index not in self._memmap_analogsignal_buffers:
self._memmap_analogsignal_buffers[block_index] = {}
if seg_index not in self._memmap_analogsignal_buffers[block_index]:
self._memmap_analogsignal_buffers[block_index][seg_index] = {}
if buffer_id not in self._memmap_analogsignal_buffers[block_index][seg_index]:
fid = open(buffer_desc["file_path"], mode="rb")
self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id] = fid
else:
fid = self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id]
if time_axis == 0:
# MULTIPLEXED: time_axis=0 means (time, channels) layout
# open files on demand and keep reference to opened file
if not hasattr(self, "_memmap_analogsignal_buffers"):
self._memmap_analogsignal_buffers = {}
if block_index not in self._memmap_analogsignal_buffers:
self._memmap_analogsignal_buffers[block_index] = {}
if seg_index not in self._memmap_analogsignal_buffers[block_index]:
self._memmap_analogsignal_buffers[block_index][seg_index] = {}
if buffer_id not in self._memmap_analogsignal_buffers[block_index][seg_index]:
fid = open(buffer_desc["file_path"], mode="rb")
self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id] = fid
else:
fid = self._memmap_analogsignal_buffers[block_index][seg_index][buffer_id]

num_channels = buffer_desc["shape"][1]

raw_sigs = get_memmap_chunk_from_opened_file(
fid,
num_channels,
i_start,
i_stop,
np.dtype(buffer_desc["dtype"]),
file_offset=buffer_desc["file_offset"],
)

num_channels = buffer_desc["shape"][1]
elif time_axis == 1:
# VECTORIZED: time_axis=1 means shape is (channels, time)
# Data is stored as [all_samples_ch1, all_samples_ch2, ...]
dtype = np.dtype(buffer_desc["dtype"])
num_channels = buffer_desc["shape"][0] # shape is (channels, time)
num_samples = i_stop - i_start
total_samples_per_channel = buffer_desc["shape"][1] # shape is (channels, time)

# Determine which channels to read
if channel_indexes is None:
chan_indices = np.arange(num_channels)
else:
chan_indices = np.asarray(channel_indexes)

raw_sigs = np.empty((num_samples, len(chan_indices)), dtype=dtype)

for i, chan_idx in enumerate(chan_indices):
offset = buffer_desc["file_offset"] + chan_idx * total_samples_per_channel * dtype.itemsize
channel_data = np.memmap(buffer_desc["file_path"], dtype=dtype, mode='r',
offset=offset, shape=(total_samples_per_channel,))
raw_sigs[:, i] = channel_data[i_start:i_stop]

# Channel slicing already done above, so skip later channel_indexes slicing
channel_indexes = None

raw_sigs = get_memmap_chunk_from_opened_file(
fid,
num_channels,
i_start,
i_stop,
np.dtype(buffer_desc["dtype"]),
file_offset=buffer_desc["file_offset"],
)
else:
raise ValueError(f"time_axis must be 0 or 1, got {time_axis}")

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

Expand Down
22 changes: 19 additions & 3 deletions neo/rawio/brainvisionrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ def _parse_header(self):
raise NeoReadWriteError(
f"Only `BINARY` format has been implemented. Current Data Format is {vhdr_header['Common Infos']['DataFormat']}"
)
if vhdr_header["Common Infos"]["DataOrientation"] != "MULTIPLEXED":

# Store the data orientation for later use in reading
self._data_orientation = vhdr_header["Common Infos"]["DataOrientation"]
if self._data_orientation not in ("MULTIPLEXED", "VECTORIZED"):
raise NeoReadWriteError(
f"Only `MULTIPLEXED` is implemented. Current Orientation is {vhdr_header['Common Infos']['DataOrientation']}"
f"Data orientation must be either `MULTIPLEXED` or `VECTORIZED`. Current Orientation is {self._data_orientation}"
)

nb_channel = int(vhdr_header["Common Infos"]["NumberOfChannels"])
Expand All @@ -87,14 +90,27 @@ def _parse_header(self):
buffer_id = "0"
self._buffer_descriptions = {0: {0: {}}}
self._stream_buffer_slice = {}
shape = get_memmap_shape(binary_filename, sig_dtype, num_channels=nb_channel, offset=0)

# time_axis indicates data layout: 0 for MULTIPLEXED (time, channels), 1 for VECTORIZED (channels, time)
time_axis = 0 if self._data_orientation == "MULTIPLEXED" else 1

# Get shape - always returns (num_samples, num_channels)
temp_shape = get_memmap_shape(binary_filename, sig_dtype, num_channels=nb_channel, offset=0)

# For consistency with HDF5 pattern: when time_axis=1, shape should be (channels, time)
if time_axis == 1:
shape = (temp_shape[1], temp_shape[0]) # (num_channels, num_samples)
else:
shape = temp_shape # (num_samples, num_channels)

self._buffer_descriptions[0][0][buffer_id] = {
"type": "raw",
"file_path": binary_filename,
"dtype": str(sig_dtype),
"order": "C",
"file_offset": 0,
"shape": shape,
"time_axis": time_axis,
}
self._stream_buffer_slice[stream_id] = None

Expand Down
Loading