42
42
imDatPrb_type=24 (NP 2.0, 4-shank)
43
43
44
44
Author : Samuel Garcia
45
+ Some functions are copied from Graham Findlay
45
46
"""
46
47
47
48
import warnings
@@ -90,6 +91,7 @@ def _parse_header(self):
90
91
for info in self .signals_info_list :
91
92
# key is (seg_index, stream_name)
92
93
key = (info ['seg_index' ], info ['stream_name' ])
94
+ assert key not in self .signals_info_dict
93
95
self .signals_info_dict [key ] = info
94
96
95
97
# create memmap
@@ -166,7 +168,7 @@ def _parse_header(self):
166
168
# need probeinterface to be installed
167
169
import probeinterface
168
170
info = self .signals_info_dict [seg_index , stream_name ]
169
- if 'imroTbl' in info ['meta' ] and info ['signal_kind ' ] == 'ap' :
171
+ if 'imroTbl' in info ['meta' ] and info ['stream_kind ' ] == 'ap' :
170
172
# only for ap channel
171
173
probe = probeinterface .read_spikeglx (info ['meta_file' ])
172
174
loc = probe .contact_positions
@@ -233,6 +235,11 @@ def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop,
233
235
def scan_files (dirname ):
234
236
"""
235
237
Scan for pairs of `.bin` and `.meta` files and return information about it.
238
+
239
+ After exploring the folder, the segment index (`seg_index`) is construct as follow:
240
+ * if only one `gate_num=0` then `trigger_num` = `seg_index`
241
+ * if only one `trigger_num=0` then `gate_num` = `seg_index`
242
+ * if both are increasing then seg_index increased by gate_num, trigger_num order.
236
243
"""
237
244
info_list = []
238
245
@@ -245,16 +252,99 @@ def scan_files(dirname):
245
252
if meta_filename .exists () and bin_filename .exists ():
246
253
meta = read_meta_file (meta_filename )
247
254
info = extract_stream_info (meta_filename , meta )
255
+
248
256
info ['meta_file' ] = str (meta_filename )
249
257
info ['bin_file' ] = str (bin_filename )
250
258
info_list .append (info )
251
259
260
+ # Let see if this will be anoying or not.
252
261
if bin_filename .stat ().st_size != meta ['fileSizeBytes' ]:
253
262
warnings .warn ('.meta file has faulty value for .bin file size on disc' )
254
263
264
+ # the segment index will depend on both 'gate_num' and 'trigger_num'
265
+ # so we order by 'gate_num' then 'trigger_num'
266
+ # None is before any int
267
+ def make_key (info ):
268
+ k0 = info ['gate_num' ]
269
+ if k0 is None :
270
+ k0 = - 1
271
+ k1 = info ['trigger_num' ]
272
+ if k1 is None :
273
+ k1 = - 1
274
+ return (k0 , k1 )
275
+ order_key = list ({make_key (info ) for info in info_list })
276
+ order_key = sorted (order_key )
277
+ for info in info_list :
278
+ info ['seg_index' ] = order_key .index (make_key (info ))
279
+
255
280
return info_list
256
281
257
282
283
+ def parse_spikeglx_fname (fname ):
284
+ """
285
+ Parse recording identifiers from a SpikeGLX style filename.
286
+
287
+ spikeglx naming follow this rules:
288
+ https://github.com/billkarsh/SpikeGLX/blob/master/Markdown/UserManual.md#gates-and-triggers
289
+
290
+ Example file name structure:
291
+ Consider the filenames: `Noise4Sam_g0_t0.nidq.bin` or `Noise4Sam_g0_t0.imec0.lf.bin`
292
+ The filenames consist of 3 or 4 parts separated by `.`
293
+ 1. "Noise4Sam_g0_t0" will be the `name` variable. This choosen by the user at recording time.
294
+ 2. "_g0_" is the "gate_num"
295
+ 3. "_t0_" is the "trigger_num"
296
+ 4. "nidq" or "imec0" will give the `device`
297
+ 5. "lf" or "ap" will be the `stream_kind`
298
+ `stream_name` variable is the concatenation of `device.stream_kind`
299
+
300
+ This function is copied/modified from Graham Findlay.
301
+
302
+ Notes:
303
+ * Sometimes the original file name is modified by the user and "_gt0_" or "_t0_"
304
+ are manually removed. In that case gate_name and trigger_num will be None.
305
+
306
+ Parameters
307
+ ---------
308
+ fname: str
309
+ The filename to parse without the extension, e.g. "my-run-name_g0_t1.imec2.lf"
310
+ Returns
311
+ -------
312
+ run_name: str
313
+ The run name, e.g. "my-run-name".
314
+ gate_num: int or None
315
+ The gate identifier, e.g. 0.
316
+ trigger_num: int or None
317
+ The trigger identifier, e.g. 1.
318
+ device: str
319
+ The probe identifier, e.g. "imec2"
320
+ stream_kind: str or None
321
+ The data type identifier, "lf" or "ap" or None
322
+ """
323
+ r = re .findall (r'(\S*)_g(\d*)_t(\d*)\.(\S*).(ap|lf)' , fname )
324
+ if len (r ) == 1 :
325
+ # standard case with probe
326
+ run_name , gate_num , trigger_num , device , stream_kind = r [0 ]
327
+ else :
328
+ r = re .findall (r'(\S*)_g(\d*)_t(\d*)\.(\S*)' , fname )
329
+ if len (r ) == 1 :
330
+ # case for nidaq
331
+ run_name , gate_num , trigger_num , device = r [0 ]
332
+ stream_kind = None
333
+ else :
334
+ # the naming do not correspond lets try something more easy
335
+ r = re .findall (r'(\S*)\.(\S*).(ap|lf)' , fname )
336
+ if len (r ) == 1 :
337
+ run_name , device , stream_kind = r [0 ]
338
+ gate_num , trigger_num = None , None
339
+
340
+ if gate_num is not None :
341
+ gate_num = int (gate_num )
342
+ if trigger_num is not None :
343
+ trigger_num = int (trigger_num )
344
+
345
+ return (run_name , gate_num , trigger_num , device , stream_kind )
346
+
347
+
258
348
def read_meta_file (meta_file ):
259
349
"""parse the meta file"""
260
350
with open (meta_file , mode = 'r' ) as f :
@@ -281,27 +371,13 @@ def extract_stream_info(meta_file, meta):
281
371
"""Extract info from the meta dict"""
282
372
283
373
num_chan = int (meta ['nSavedChans' ])
374
+ fname = Path (meta_file ).stem
375
+ run_name , gate_num , trigger_num , device , stream_kind = parse_spikeglx_fname (fname )
376
+ device = fname .split ('.' )[1 ]
284
377
285
- # Example file name structure:
286
- # Consider the filenames: `Noise4Sam_g0_t0.nidq.bin` or `Noise4Sam_g0_t0.imec0.lf.bin`
287
- # The filenames consist of 3 or 4 parts separated by `.`
288
- # 1. "Noise4Sam_g0_t0" will be the `name` variable. This is chosen by the user
289
- # at recording time.
290
- # 2. "_gt0_" will give the `seg_index` (here 0)
291
- # 3. "nidq" or "imec0" will give the `device` variable
292
- # 4. "lf" or "ap" will be the `signal_kind` variable
293
- # `stream_name` variable is the concatenation of `device.signal_kind`
294
- name = Path (meta_file ).stem
295
- r = re .findall (r'_g(\d*)_t' , name )
296
- if len (r ) == 0 :
297
- # when manual renaming _g0_ can be removed
298
- seg_index = 0
299
- else :
300
- seg_index = int (r [0 ][0 ])
301
- device = name .split ('.' )[1 ]
302
378
if 'imec' in device :
303
- signal_kind = name .split ('.' )[2 ]
304
- stream_name = device + '.' + signal_kind
379
+ stream_kind = fname .split ('.' )[2 ]
380
+ stream_name = device + '.' + stream_kind
305
381
units = 'uV'
306
382
# please note the 1e6 in gain for this uV
307
383
@@ -313,16 +389,16 @@ def extract_stream_info(meta_file, meta):
313
389
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_3A.md#imec
314
390
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_3B1.md#imec
315
391
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_3B2.md#imec
316
- if signal_kind == 'ap' :
392
+ if stream_kind == 'ap' :
317
393
index_imroTbl = 3
318
- elif signal_kind == 'lf' :
394
+ elif stream_kind == 'lf' :
319
395
index_imroTbl = 4
320
396
for c in range (num_chan - 1 ):
321
397
v = meta ['imroTbl' ][c ].split (' ' )[index_imroTbl ]
322
398
per_channel_gain [c ] = 1. / float (v )
323
399
gain_factor = float (meta ['imAiRangeMax' ]) / 512
324
400
channel_gains = gain_factor * per_channel_gain * 1e6
325
- elif meta ['imDatPrb_type' ] in ('21' , '24' ) and signal_kind == 'ap' :
401
+ elif meta ['imDatPrb_type' ] in ('21' , '24' ) and stream_kind == 'ap' :
326
402
# This work with NP 2.0 case with different metadata versions
327
403
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_20.md#channel-entries-by-type
328
404
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_20.md#imec
@@ -334,7 +410,7 @@ def extract_stream_info(meta_file, meta):
334
410
raise NotImplementedError ('This meta file version of spikeglx'
335
411
'is not implemented' )
336
412
else :
337
- signal_kind = ''
413
+ stream_kind = ''
338
414
stream_name = device
339
415
units = 'V'
340
416
channel_gains = np .ones (num_chan )
@@ -352,17 +428,18 @@ def extract_stream_info(meta_file, meta):
352
428
channel_gains = per_channel_gain * gain_factor
353
429
354
430
info = {}
355
- info ['name ' ] = name
431
+ info ['fname ' ] = fname
356
432
info ['meta' ] = meta
357
433
for k in ('niSampRate' , 'imSampRate' ):
358
434
if k in meta :
359
435
info ['sampling_rate' ] = float (meta [k ])
360
436
info ['num_chan' ] = num_chan
361
437
362
438
info ['sample_length' ] = int (meta ['fileSizeBytes' ]) // 2 // num_chan
363
- info ['seg_index' ] = seg_index
439
+ info ['gate_num' ] = gate_num
440
+ info ['trigger_num' ] = trigger_num
364
441
info ['device' ] = device
365
- info ['signal_kind ' ] = signal_kind
442
+ info ['stream_kind ' ] = stream_kind
366
443
info ['stream_name' ] = stream_name
367
444
info ['units' ] = units
368
445
info ['channel_names' ] = [txt .split (';' )[0 ] for txt in meta ['snsChanMap' ]]
0 commit comments