|
4 | 4 | from .utils import convert_to_number |
5 | 5 |
|
6 | 6 |
|
| 7 | +AP_GAIN = 80 # For NP 2.0 probes; APGain = 80 for all AP (LF is computed from AP) |
| 8 | + |
| 9 | +# Imax values for different probe types - see metaguides (http://billkarsh.github.io/SpikeGLX/#metadata-guides) |
| 10 | +IMAX = {'neuropixels 1.0 - 3A': 512, |
| 11 | + 'neuropixels 1.0 - 3B': 512, |
| 12 | + 'neuropixels 2.0 - SS': 8192, |
| 13 | + 'neuropixels 2.0 - MS': 8192} |
| 14 | + |
| 15 | + |
7 | 16 | class SpikeGLX: |
8 | 17 |
|
9 | 18 | def __init__(self, root_dir): |
@@ -69,26 +78,31 @@ def get_channel_bit_volts(self, band='ap'): |
69 | 78 | """ |
70 | 79 | Extract the AP and LF channels' int16 to microvolts |
71 | 80 | Following the steps specified in: https://billkarsh.github.io/SpikeGLX/Support/SpikeGLX_Datafile_Tools.zip |
72 | | - dataVolts = dataInt * fI2V / gain |
| 81 | + dataVolts = dataInt * Vmax / Imax / gain |
73 | 82 | """ |
74 | | - fI2V = float(self.apmeta.meta['imAiRangeMax']) / 512 |
| 83 | + vmax = float(self.apmeta.meta['imAiRangeMax']) |
75 | 84 |
|
76 | 85 | if band == 'ap': |
| 86 | + imax = IMAX[self.apmeta.probe_model] |
77 | 87 | imroTbl_data = self.apmeta.imroTbl['data'] |
78 | 88 | imroTbl_idx = 3 |
| 89 | + |
79 | 90 | elif band == 'lf': |
| 91 | + imax = IMAX[self.lfmeta.probe_model] |
80 | 92 | imroTbl_data = self.lfmeta.imroTbl['data'] |
81 | 93 | imroTbl_idx = 4 |
| 94 | + else: |
| 95 | + raise ValueError(f'Unsupported band: {band} - Must be "ap" or "lf"') |
82 | 96 |
|
83 | 97 | # extract channels' gains |
84 | 98 | if 'imDatPrb_dock' in self.apmeta.meta: |
85 | 99 | # NP 2.0; APGain = 80 for all AP (LF is computed from AP) |
86 | | - chn_gains = [80] * len(imroTbl_data) |
| 100 | + chn_gains = [AP_GAIN] * len(imroTbl_data) |
87 | 101 | else: |
88 | 102 | # 3A, 3B1, 3B2 (NP 1.0) |
89 | 103 | chn_gains = [c[imroTbl_idx] for c in imroTbl_data] |
90 | 104 |
|
91 | | - return fI2V / np.array(chn_gains) * 1e6 # convert to uV as well |
| 105 | + return vmax / imax / np.array(chn_gains) * 1e6 # convert to uV as well |
92 | 106 |
|
93 | 107 | def _read_bin(self, fname): |
94 | 108 | nchan = self.apmeta.meta['nSavedChans'] |
@@ -245,23 +259,28 @@ def _parse_imrotbl(raw): |
245 | 259 |
|
246 | 260 | @property |
247 | 261 | def recording_channels(self): |
| 262 | + """ |
| 263 | + Because you can selectively save channels, the |
| 264 | + ith channel in the file isn't necessarily the ith acquired channel. |
| 265 | + Use this function to convert from ith stored to original index. |
| 266 | +
|
| 267 | + Credit to https://billkarsh.github.io/SpikeGLX/Support/SpikeGLX_Datafile_Tools.zip |
| 268 | + OriginalChans() function |
| 269 | + """ |
248 | 270 | if self._recording_channels is None: |
249 | 271 | if self.meta['snsSaveChanSubset'] == 'all': |
250 | 272 | # output = int32, 0 to nSavedChans - 1 |
251 | 273 | self._recording_channels = np.arange(0, int(self.meta['nSavedChans'])) |
252 | 274 | else: |
253 | 275 | # parse the snsSaveChanSubset string |
254 | 276 | # split at commas |
255 | | - chStrList = self.meta['snsSaveChanSubset'].split(sep = ',') |
| 277 | + chStrList = self.meta['snsSaveChanSubset'].split(sep=',') |
256 | 278 | self._recording_channels = np.arange(0, 0) # creates an empty array of int32 |
257 | 279 | for sL in chStrList: |
258 | | - currList = sL.split(sep = ':') |
259 | | - if len(currList) > 1: |
260 | | - # each set of contiguous channels specified by |
261 | | - # chan1:chan2 inclusive |
262 | | - newChans = np.arange(int(currList[0]), int(currList[1]) + 1) |
263 | | - else: |
264 | | - newChans = np.arange(int(currList[0]), int(currList[0]) + 1) |
| 280 | + currList = sL.split(sep=':') |
| 281 | + # each set of continuous channels specified by chan1:chan2 inclusive |
| 282 | + newChans = np.arange(int(currList[0]), int(currList[min(1, len(currList))]) + 1) |
| 283 | + |
265 | 284 | self._recording_channels = np.append(self._recording_channels, newChans) |
266 | 285 | return self._recording_channels |
267 | 286 |
|
|
0 commit comments