Skip to content

Commit 561458e

Browse files
committed
wip parse fname and refactor seg_index concept
1 parent 2b0550e commit 561458e

File tree

2 files changed

+106
-30
lines changed

2 files changed

+106
-30
lines changed

neo/rawio/spikeglxrawio.py

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
imDatPrb_type=24 (NP 2.0, 4-shank)
4343
4444
Author : Samuel Garcia
45+
Some functions are copied from Graham Findlay
4546
"""
4647

4748
from .baserawio import (BaseRawIO, _signal_channel_dtype, _signal_stream_dtype,
@@ -165,7 +166,7 @@ def _parse_header(self):
165166
# need probeinterface to be installed
166167
import probeinterface
167168
info = self.signals_info_dict[seg_index, stream_name]
168-
if 'imroTbl' in info['meta'] and info['signal_kind'] == 'ap':
169+
if 'imroTbl' in info['meta'] and info['stream_kind'] == 'ap':
169170
# only for ap channel
170171
probe = probeinterface.read_spikeglx(info['meta_file'])
171172
loc = probe.contact_positions
@@ -232,6 +233,12 @@ def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop,
232233
def scan_files(dirname):
233234
"""
234235
Scan for pairs of `.bin` and `.meta` files and return information about it.
236+
237+
After exploring the folder, the segment index (`seg_index`) is construct as follow:
238+
* if only one `gate_num=0` then `trigger_num` = `seg_index`
239+
* if only one `trigger_num=0` then `gate_num` = `seg_index`
240+
* if both are increasing then seg_index increased by gate_num, trigger_num order.
241+
235242
"""
236243
info_list = []
237244

@@ -247,10 +254,83 @@ def scan_files(dirname):
247254
info['meta_file'] = str(meta_filename)
248255
info['bin_file'] = str(bin_filename)
249256
info_list.append(info)
250-
257+
258+
total_gate = max([info['gate_num'] for info in info_list]) + 1
259+
total_trigger_per_gate = []
260+
for gate_num in range(total_gate):
261+
max_trigger = 0
262+
for info in info_list:
263+
if info['gate_num'] != gate_num:
264+
continue
265+
max_trigger = max(max_trigger, info['trigger_num'])
266+
total_trigger_per_gate.append(max_trigger+1)
267+
268+
for info in info_list:
269+
g, t = info['gate_num'], info['trigger_num']
270+
if g == 0:
271+
seg_index = t
272+
else:
273+
seg_index = sum(total_trigger_per_gate[:g]) + t
274+
info['seg_index'] = seg_index
275+
251276
return info_list
252277

253278

279+
def parse_spikeglx_fname(fname):
280+
"""
281+
Parse recording identifiers from a SpikeGLX style filename.
282+
283+
spikeglx naming follow this rules:
284+
https://github.com/billkarsh/SpikeGLX/blob/master/Markdown/UserManual.md#gates-and-triggers
285+
286+
Example file name structure:
287+
Consider the filenames: `Noise4Sam_g0_t0.nidq.bin` or `Noise4Sam_g0_t0.imec0.lf.bin`
288+
The filenames consist of 3 or 4 parts separated by `.`
289+
1. "Noise4Sam_g0_t0" will be the `name` variable. This choosen by the user at recording time.
290+
2. "_g0_" is the "gate_num"
291+
3. "_t0_" is the "trigger_num"
292+
4. "nidq" or "imec0" will give the `device`
293+
5. "lf" or "ap" will be the `stream_kind`
294+
`stream_name` variable is the concatenation of `device.stream_kind`
295+
296+
This function is copied/modified from Graham Findlay.
297+
298+
Notes:
299+
* Sometimes the original file name is modified by the user and "_gt0_" or "_t0_"
300+
are manually removed. In that case gate_name and trigger_num will be None.
301+
302+
Parameters
303+
---------
304+
fname: str
305+
The filename to parse without the extension, e.g. "my-run-name_g0_t1.imec2.lf"
306+
Returns
307+
-------
308+
run_name: str
309+
The run name, e.g. "my-run-name".
310+
gate_num: int or None
311+
The gate identifier, e.g. 0.
312+
trigger_num: int or None
313+
The trigger identifier, e.g. 1.
314+
device: str
315+
The probe identifier, e.g. "imec2"
316+
stream_kind: str
317+
The data type identifier, "lf" or "ap"
318+
"""
319+
r = re.findall(r'(\S*)_g(\d*)_t(\d*)\.(\S*).(ap|lf)', fname)
320+
if len(r) >0:
321+
# standard case
322+
run_name, gate_num, trigger_num, device, stream_kind = r[0]
323+
gate_num = int(gate_num)
324+
trigger_num = int(trigger_num)
325+
else:
326+
# the naming do not correspond lets try something more easy
327+
r = re.findall(r'(\S*)\.(\S*).(ap|lf)', fname)
328+
if len(r) > 0:
329+
run_name, device, stream_kind = r[0]
330+
gate_num, trigger_num = None, None
331+
return (run_name, gate_num, trigger_num, device, stream_kind)
332+
333+
254334
def read_meta_file(meta_file):
255335
"""parse the meta file"""
256336
with open(meta_file, mode='r') as f:
@@ -277,27 +357,15 @@ def extract_stream_info(meta_file, meta):
277357
"""Extract info from the meta dict"""
278358

279359
num_chan = int(meta['nSavedChans'])
280-
281-
# Example file name structure:
282-
# Consider the filenames: `Noise4Sam_g0_t0.nidq.bin` or `Noise4Sam_g0_t0.imec0.lf.bin`
283-
# The filenames consist of 3 or 4 parts separated by `.`
284-
# 1. "Noise4Sam_g0_t0" will be the `name` variable. This choosen by the user
285-
# at recording time.
286-
# 2. "_gt0_" will give the `seg_index` (here 0)
287-
# 3. "nidq" or "imec0" will give the `device` variable
288-
# 4. "lf" or "ap" will be the `signal_kind` variable
289-
# `stream_name` variable is the concatenation of `device.signal_kind`
290-
name = Path(meta_file).stem
291-
r = re.findall(r'_g(\d*)_t', name)
292-
if len(r) == 0:
293-
# when manual renaming _g0_ can be removed
294-
seg_index = 0
295-
else:
296-
seg_index = int(r[0][0])
297-
device = name.split('.')[1]
360+
361+
fname = Path(meta_file).stem
362+
363+
run_name, gate_num, trigger_num, device, stream_kind = parse_spikeglx_fname(fname)
364+
365+
device = fname.split('.')[1]
298366
if 'imec' in device:
299-
signal_kind = name.split('.')[2]
300-
stream_name = device + '.' + signal_kind
367+
stream_kind = fname.split('.')[2]
368+
stream_name = device + '.' + stream_kind
301369
units = 'uV'
302370
# please note the 1e6 in gain for this uV
303371

@@ -309,16 +377,16 @@ def extract_stream_info(meta_file, meta):
309377
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_3A.md#imec
310378
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_3B1.md#imec
311379
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_3B2.md#imec
312-
if signal_kind == 'ap':
380+
if stream_kind == 'ap':
313381
index_imroTbl = 3
314-
elif signal_kind == 'lf':
382+
elif stream_kind == 'lf':
315383
index_imroTbl = 4
316384
for c in range(num_chan - 1):
317385
v = meta['imroTbl'][c].split(' ')[index_imroTbl]
318386
per_channel_gain[c] = 1. / float(v)
319387
gain_factor = float(meta['imAiRangeMax']) / 512
320388
channel_gains = gain_factor * per_channel_gain * 1e6
321-
elif meta['imDatPrb_type'] in ('21', '24') and signal_kind == 'ap':
389+
elif meta['imDatPrb_type'] in ('21', '24') and stream_kind == 'ap':
322390
# This work with NP 2.0 case with different metadata versions
323391
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_20.md#channel-entries-by-type
324392
# https://github.com/billkarsh/SpikeGLX/blob/gh-pages/Support/Metadata_20.md#imec
@@ -330,7 +398,7 @@ def extract_stream_info(meta_file, meta):
330398
raise NotImplementedError('This meta file version of spikeglx'
331399
'is not implemented')
332400
else:
333-
signal_kind = ''
401+
stream_kind = ''
334402
stream_name = device
335403
units = 'V'
336404
channel_gains = np.ones(num_chan)
@@ -348,17 +416,19 @@ def extract_stream_info(meta_file, meta):
348416
channel_gains = per_channel_gain * gain_factor
349417

350418
info = {}
351-
info['name'] = name
419+
info['fname'] = fname
352420
info['meta'] = meta
353421
for k in ('niSampRate', 'imSampRate'):
354422
if k in meta:
355423
info['sampling_rate'] = float(meta[k])
356424
info['num_chan'] = num_chan
357425

358426
info['sample_length'] = int(meta['fileSizeBytes']) // 2 // num_chan
359-
info['seg_index'] = seg_index
427+
#~ info['seg_index'] = seg_index
428+
info['gate_num'] = gate_num
429+
info['trigger_num'] = trigger_num
360430
info['device'] = device
361-
info['signal_kind'] = signal_kind
431+
info['stream_kind'] = stream_kind
362432
info['stream_name'] = stream_name
363433
info['units'] = units
364434
info['channel_names'] = [txt.split(';')[0] for txt in meta['snsChanMap']]

neo/test/rawiotest/test_spikeglxrawio.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ class TestSpikeGLXRawIO(BaseTestRawIO, unittest.TestCase):
1515
]
1616
entities_to_test = [
1717
'spikeglx/Noise4Sam_g0',
18-
'spikeglx/TEST_20210920_0_g0'
18+
'spikeglx/TEST_20210920_0_g0',
19+
'spikeglx/sample_data_v2/SpikeGLX/5-19-2022-CI0',
20+
'spikeglx/sample_data_v2/SpikeGLX/5-19-2022-CI1',
21+
'spikeglx/sample_data_v2/SpikeGLX/5-19-2022-CI2',
22+
'spikeglx/sample_data_v2/SpikeGLX/5-19-2022-CI3',
23+
'spikeglx/sample_data_v2/SpikeGLX/5-19-2022-CI4',
24+
'spikeglx/sample_data_v2/SpikeGLX/5-19-2022-CI5',
1925
]
2026

2127
def test_with_location(self):

0 commit comments

Comments
 (0)