5353from pathlib import Path
5454import os
5555import re
56+ from warnings import warn
5657
5758import numpy as np
5859
@@ -76,7 +77,9 @@ class SpikeGLXRawIO(BaseRawWithBufferApiIO):
7677 dirname: str, default: ''
7778 The spikeglx folder containing meta/bin files
7879 load_sync_channel: bool, default: False
79- The last channel (SY0) of each stream is a fake channel used for synchronisation
80+ Can be used to load the synch stream as the last channel of the neural data.
81+ This option is deprecated and will be removed in version 0.15.
82+ From versions higher than 0.14.1 the sync channel is always loaded as a separate stream.
8083 load_channel_location: bool, default: False
8184 If True probeinterface is used to load the channel locations from the directory
8285
@@ -109,6 +112,13 @@ def __init__(self, dirname="", load_sync_channel=False, load_channel_location=Fa
109112 BaseRawWithBufferApiIO .__init__ (self )
110113 self .dirname = dirname
111114 self .load_sync_channel = load_sync_channel
115+ if load_sync_channel :
116+ warn (
117+ "The load_sync_channel=True option is deprecated and will be removed in version 0.15 \n "
118+ "The sync channel is now loaded as a separate stream by default and should be accessed as such. " ,
119+ DeprecationWarning ,
120+ stacklevel = 2 ,
121+ )
112122 self .load_channel_location = load_channel_location
113123
114124 def _source_name (self ):
@@ -152,6 +162,8 @@ def _parse_header(self):
152162 signal_buffers = []
153163 signal_streams = []
154164 signal_channels = []
165+ sync_stream_id_to_buffer_id = {}
166+
155167 for stream_name in stream_names :
156168 # take first segment
157169 info = self .signals_info_dict [0 , stream_name ]
@@ -168,6 +180,21 @@ def _parse_header(self):
168180 for local_chan in range (info ["num_chan" ]):
169181 chan_name = info ["channel_names" ][local_chan ]
170182 chan_id = f"{ stream_name } #{ chan_name } "
183+
184+ # Sync channel
185+ if (
186+ "nidq" not in stream_name
187+ and "SY0" in chan_name
188+ and not self .load_sync_channel
189+ and local_chan == info ["num_chan" ] - 1
190+ ):
191+ # This is a sync channel and should be added as its own stream
192+ sync_stream_id = f"{ stream_name } -SYNC"
193+ sync_stream_id_to_buffer_id [sync_stream_id ] = buffer_id
194+ stream_id_for_chan = sync_stream_id
195+ else :
196+ stream_id_for_chan = stream_id
197+
171198 signal_channels .append (
172199 (
173200 chan_name ,
@@ -177,25 +204,33 @@ def _parse_header(self):
177204 info ["units" ],
178205 info ["channel_gains" ][local_chan ],
179206 info ["channel_offsets" ][local_chan ],
180- stream_id ,
207+ stream_id_for_chan ,
181208 buffer_id ,
182209 )
183210 )
184211
185- # all channel by dafult unless load_sync_channel=False
212+ # all channel by default unless load_sync_channel=False
186213 self ._stream_buffer_slice [stream_id ] = None
214+
187215 # check sync channel validity
188216 if "nidq" not in stream_name :
189217 if not self .load_sync_channel and info ["has_sync_trace" ]:
190- # the last channel is remove from the stream but not from the buffer
191- last_chan = signal_channels [- 1 ]
192- last_chan = last_chan [:- 2 ] + ("" , buffer_id )
193- signal_channels = signal_channels [:- 1 ] + [last_chan ]
218+ # the last channel is removed from the stream but not from the buffer
194219 self ._stream_buffer_slice [stream_id ] = slice (0 , - 1 )
220+
221+ # Add a buffer slice for the sync channel
222+ sync_stream_id = f"{ stream_name } -SYNC"
223+ self ._stream_buffer_slice [sync_stream_id ] = slice (- 1 , None )
224+
195225 if self .load_sync_channel and not info ["has_sync_trace" ]:
196226 raise ValueError ("SYNC channel is not present in the recording. " "Set load_sync_channel to False" )
197227
198228 signal_buffers = np .array (signal_buffers , dtype = _signal_buffer_dtype )
229+
230+ # Add sync channels as their own streams
231+ for sync_stream_id , buffer_id in sync_stream_id_to_buffer_id .items ():
232+ signal_streams .append ((sync_stream_id , sync_stream_id , buffer_id ))
233+
199234 signal_streams = np .array (signal_streams , dtype = _signal_stream_dtype )
200235 signal_channels = np .array (signal_channels , dtype = _signal_channel_dtype )
201236
@@ -237,6 +272,14 @@ def _parse_header(self):
237272 t_start = frame_start / sampling_frequency
238273
239274 self ._t_starts [stream_name ][seg_index ] = t_start
275+
276+ # This need special logic because sync not present in stream_names
277+ if f"{ stream_name } -SYNC" in signal_streams ["name" ]:
278+ sync_stream_name = f"{ stream_name } -SYNC"
279+ if sync_stream_name not in self ._t_starts :
280+ self ._t_starts [sync_stream_name ] = {}
281+ self ._t_starts [sync_stream_name ][seg_index ] = t_start
282+
240283 t_stop = info ["sample_length" ] / info ["sampling_rate" ]
241284 self ._t_stops [seg_index ] = max (self ._t_stops [seg_index ], t_stop )
242285
@@ -266,6 +309,10 @@ def _parse_header(self):
266309 # need probeinterface to be installed
267310 import probeinterface
268311
312+ # Skip for sync streams
313+ if "SYNC" in stream_name :
314+ continue
315+
269316 info = self .signals_info_dict [seg_index , stream_name ]
270317 if "imroTbl" in info ["meta" ] and info ["stream_kind" ] == "ap" :
271318 # only for ap channel
@@ -529,7 +576,7 @@ def extract_stream_info(meta_file, meta):
529576 if (
530577 "imDatPrb_type" not in meta
531578 or meta ["imDatPrb_type" ] == "0"
532- or meta ["imDatPrb_type" ] in ("1015" , "1016" , "1022" , "1030" , "1031" , "1032" , "1100" , "1121" , "1300" )
579+ or meta ["imDatPrb_type" ] in ("1015" , "1016" , "1022" , "1030" , "1031" , "1032" , "1100" , "1121" , "1123" , " 1300" )
533580 ):
534581 # This work with NP 1.0 case with different metadata versions
535582 # https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md
0 commit comments