Skip to content

Commit 18a906f

Browse files
committed
refactor plexon rawio to have same ids as plexon2
1 parent 60b26d4 commit 18a906f

File tree

1 file changed

+53
-46
lines changed

1 file changed

+53
-46
lines changed

neo/rawio/plexonrawio.py

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,19 @@ def _parse_header(self):
230230
self._data_blocks[bl_type][chan_id] = data_block
231231

232232
# signals channels
233-
sig_channels = []
234-
all_sig_length = []
235233
source_id = []
234+
235+
# Scanning sources and populating signal channels at the same time. Sources have to have
236+
# same sampling rate and number of samples to belong to one stream.
237+
signal_channels = []
238+
channel_num_samples = []
239+
240+
# We will build the stream ids based on the channel prefixes
241+
# The channel prefixes are the first characters of the channel names which have the following format:
242+
# WB{number}, FPX{number}, SPKCX{number}, AI{number}, etc
243+
# We will extract the prefix and use it as stream id
244+
regex_prefix_pattern = r"^\D+" # Match any non-digit character at the beginning of the string
245+
236246
if self.progress_bar:
237247
chan_loop = trange(nb_sig_chan, desc="Parsing signal channels", leave=True)
238248
else:
@@ -245,7 +255,7 @@ def _parse_header(self):
245255
if length == 0:
246256
continue # channel not added
247257
source_id.append(h["SrcId"])
248-
all_sig_length.append(length)
258+
channel_num_samples.append(length)
249259
sampling_rate = float(h["ADFreq"])
250260
sig_dtype = "int16"
251261
units = "" # I don't know units
@@ -258,61 +268,57 @@ def _parse_header(self):
258268
0.5 * (2 ** global_header["BitsPerSpikeSample"]) * h["Gain"] * h["PreampGain"]
259269
)
260270
offset = 0.0
261-
stream_id = "0" # This is overwritten later
262-
sig_channels.append((name, str(chan_id), sampling_rate, sig_dtype, units, gain, offset, stream_id))
271+
channel_prefix = re.match(regex_prefix_pattern, name).group(0)
272+
stream_id = channel_prefix
273+
274+
signal_channels.append((name, str(chan_id), sampling_rate, sig_dtype, units, gain, offset, stream_id))
263275

264-
sig_channels = np.array(sig_channels, dtype=_signal_channel_dtype)
276+
signal_channels = np.array(signal_channels, dtype=_signal_channel_dtype)
265277

266-
if sig_channels.size == 0:
278+
if signal_channels.size == 0:
267279
signal_streams = np.array([], dtype=_signal_stream_dtype)
268280

269281
else:
270282
# Detect streams
271-
all_sig_length = np.asarray(all_sig_length)
272-
273-
# names are WB{number}, FPX{number}, SPKCX{number}, AI{number}, etc
274-
pattern = r"^\D+" # Match any non-digit character at the beginning of the string
275-
channels_prefixes = np.asarray([re.match(pattern, name).group(0) for name in sig_channels["name"]])
276-
buffer_stream_groups = set(zip(channels_prefixes, sig_channels["sampling_rate"], all_sig_length))
277-
278-
# There are explanations of the streams based on channel names
279-
# provided by a Plexon Engineer, see here:
283+
channel_num_samples = np.asarray(channel_num_samples)
284+
# We are using channel prefixes as stream_ids
285+
# The meaning of the channel prefixes was provided by a Plexon Engineer, see here:
280286
# https://github.com/NeuralEnsemble/python-neo/pull/1495#issuecomment-2184256894
281-
channel_prefix_to_stream_name = {
287+
stream_id_to_stream_name = {
282288
"WB": "WB-Wideband",
283-
"FP": "FPl-Low Pass Filtered ",
289+
"FP": "FPl-Low Pass Filtered",
284290
"SP": "SPKC-High Pass Filtered",
285291
"AI": "AI-Auxiliary Input",
286292
}
287293

288-
# Using a mapping to ensure consistent order of stream_index
289-
channel_prefix_to_stream_id = {
290-
"WB": "0",
291-
"FP": "1",
292-
"SP": "2",
293-
"AI": "3",
294-
}
295-
294+
unique_stream_ids = np.unique(signal_channels["stream_id"])
296295
signal_streams = []
297-
self._signal_length = {}
298-
self._sig_sampling_rate = {}
299-
300-
for stream_index, (channel_prefix, sr, length) in enumerate(buffer_stream_groups):
301-
# The users of plexon can modify the prefix of the channel names (e.g. `my_prefix` instead of `WB`). This is not common but in that case
302-
# We assign the channel_prefix both as stream_name and stream_id
303-
stream_name = channel_prefix_to_stream_name.get(channel_prefix, channel_prefix)
304-
stream_id = channel_prefix_to_stream_id.get(channel_prefix, channel_prefix)
305-
306-
mask = (sig_channels["sampling_rate"] == sr) & (all_sig_length == length)
307-
sig_channels["stream_id"][mask] = stream_id
308-
309-
self._sig_sampling_rate[stream_index] = sr
310-
self._signal_length[stream_index] = length
311-
296+
for stream_id in unique_stream_ids:
297+
# We are using the channel prefixes as ids
298+
# The users of plexon can modify the prefix of the channel names (e.g. `my_prefix` instead of `WB`).
299+
# In that case we use the channel prefix both as stream id and name
300+
stream_name = stream_id_to_stream_name.get(stream_id, stream_id)
312301
signal_streams.append((stream_name, stream_id))
313302

314303
signal_streams = np.array(signal_streams, dtype=_signal_stream_dtype)
315304

305+
self.stream_id_samples = {}
306+
self.stream_id_sampling_frequency = {}
307+
self.stream_index_to_stream_id = {}
308+
for stream_index, stream_id in enumerate(signal_streams["id"]):
309+
# Keep a mapping from stream_index to stream_id
310+
self.stream_index_to_stream_id[stream_index] = stream_id
311+
312+
# We extract the number of samples for each stream
313+
mask = signal_channels["stream_id"] == stream_id
314+
signal_num_samples = np.unique(channel_num_samples[mask])
315+
assert signal_num_samples.size == 1, "All channels in a stream must have the same number of samples"
316+
self.stream_id_samples[stream_id] = signal_num_samples[0]
317+
318+
signal_sampling_frequency = np.unique(signal_channels[mask]["sampling_rate"])
319+
assert signal_sampling_frequency.size == 1, "All channels in a stream must have the same sampling frequency"
320+
self.stream_id_sampling_frequency[stream_id] = signal_sampling_frequency[0]
321+
316322
self._global_ssampling_rate = global_header["ADFrequency"]
317323

318324
# Determine number of units per channels
@@ -374,7 +380,7 @@ def _parse_header(self):
374380
"nb_block": 1,
375381
"nb_segment": [1],
376382
"signal_streams": signal_streams,
377-
"signal_channels": sig_channels,
383+
"signal_channels": signal_channels,
378384
"spike_channels": spike_channels,
379385
"event_channels": event_channels,
380386
}
@@ -392,14 +398,15 @@ def _segment_t_start(self, block_index, seg_index):
392398

393399
def _segment_t_stop(self, block_index, seg_index):
394400
t_stop = float(self._last_timestamps) / self._global_ssampling_rate
395-
if hasattr(self, "_signal_length"):
396-
for stream_index in self._signal_length.keys():
397-
t_stop_sig = self._signal_length[stream_index] / self._sig_sampling_rate[stream_index]
401+
if hasattr(self, "stream_id_samples"):
402+
for stream_id in self.stream_id_samples.keys():
403+
t_stop_sig = self.stream_id_samples[stream_id] / self.stream_id_sampling_frequency[stream_id]
398404
t_stop = max(t_stop, t_stop_sig)
399405
return t_stop
400406

401407
def _get_signal_size(self, block_index, seg_index, stream_index):
402-
return self._signal_length[stream_index]
408+
stream_id = self.stream_index_to_stream_id[stream_index]
409+
return self.stream_id_samples[stream_id]
403410

404411
def _get_signal_t_start(self, block_index, seg_index, stream_index):
405412
return 0.0

0 commit comments

Comments
 (0)