Skip to content

Commit a50e874

Browse files
committed
Merge branch 'develop' into single_source_version
2 parents b8bd838 + cb03e7b commit a50e874

File tree

14 files changed

+913
-137
lines changed

14 files changed

+913
-137
lines changed

CITATION.cff

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cff-version: 0.0.0
2+
message: "If you use this software, please cite it as below."
3+
authors:
4+
- family-names: International Brain Laboratory
5+
given-names: The
6+
orcid:
7+
title: "ibllib"
8+
version:
9+
doi:
10+
date-released: 2021-12-09
11+
url: "https://github.com/int-brain-lab/ibllib"

brainbox/behavior/dlc.py

Lines changed: 329 additions & 7 deletions
Large diffs are not rendered by default.

brainbox/behavior/wheel.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import numpy as np
55
from numpy import pi
66
import scipy.interpolate as interpolate
7-
from scipy.signal import convolve, gaussian
7+
from scipy.signal import convolve, windows
88
from scipy.linalg import hankel
99
import matplotlib.pyplot as plt
1010
from matplotlib.collections import LineCollection
@@ -114,7 +114,7 @@ def velocity_smoothed(pos, freq, smooth_size=0.03):
114114
std_samps = np.round(smooth_size * freq) # Standard deviation relative to sampling frequency
115115
N = std_samps * 6 # Number of points in the Gaussian covering +/-3 standard deviations
116116
gauss_std = (N - 1) / 6
117-
win = gaussian(N, gauss_std)
117+
win = windows.gaussian(N, gauss_std)
118118
win = win / win.sum() # Normalize amplitude
119119

120120
# Convolve and multiply by sampling frequency to restore original units
@@ -274,7 +274,7 @@ def movements(t, pos, freq=1000, pos_thresh=8, t_thresh=.2, min_gap=.1, pos_thre
274274
peak_amps = np.fromiter(peaks, dtype=float, count=onsets.size)
275275
N = 10 # Number of points in the Gaussian
276276
STDEV = 1.8 # Equivalent to a width factor (alpha value) of 2.5
277-
gauss = gaussian(N, STDEV) # A 10-point Gaussian window of a given s.d.
277+
gauss = windows.gaussian(N, STDEV) # A 10-point Gaussian window of a given s.d.
278278
vel = convolve(np.diff(np.insert(pos, 0, 0)), gauss, mode='same')
279279
# For each movement period, find the timestamp where the absolute velocity was greatest
280280
peaks = (t[m + np.abs(vel[m:n]).argmax()] for m, n in zip(onset_samps, offset_samps))

brainbox/io/one.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from ibllib.io import spikeglx
1414
from ibllib.io.extractors.training_wheel import extract_wheel_moves, extract_first_movement_times
15-
from ibllib.ephys.neuropixel import SITES_COORDINATES, TIP_SIZE_UM
15+
from ibllib.ephys.neuropixel import SITES_COORDINATES, TIP_SIZE_UM, trace_header
1616
from ibllib.atlas import atlas
1717
from ibllib.atlas import AllenAtlas
1818
from ibllib.pipes import histology
@@ -221,7 +221,7 @@ def _load_channels_locations_from_disk(eid, collection=None, one=None, revision=
221221
return channels
222222

223223

224-
def channel_locations_interpolation(channels_aligned, channels, brain_regions=None):
224+
def channel_locations_interpolation(channels_aligned, channels=None, brain_regions=None):
225225
"""
226226
oftentimes the channel map for different spike sorters may be different so interpolate the alignment onto
227227
if there is no spike sorting in the base folder, the alignment doesn't have the localCoordinates field
@@ -238,16 +238,18 @@ def channel_locations_interpolation(channels_aligned, channels, brain_regions=No
238238
'x', 'y', 'z', 'acronym', 'atlas_id', 'axial_um', 'lateral_um'
239239
:return: Bunch or dictionary of channels with brain coordinates keys
240240
"""
241+
NEUROPIXEL_VERSION = 1
242+
h = trace_header(version=NEUROPIXEL_VERSION)
243+
if channels is None:
244+
channels = {'localCoordinates': np.c_[h['x'], h['y']]}
241245
nch = channels['localCoordinates'].shape[0]
242246
if set(['x', 'y', 'z']).issubset(set(channels_aligned.keys())):
243247
channels_aligned = _channels_bunch2alf(channels_aligned)
244248
if 'localCoordinates' in channels_aligned.keys():
245249
aligned_depths = channels_aligned['localCoordinates'][:, 1]
246250
else: # this is a edge case for a few spike sorting sessions
247251
assert channels_aligned['mlapdv'].shape[0] == 384
248-
NEUROPIXEL_VERSION = 1
249-
from ibllib.ephys.neuropixel import trace_header
250-
aligned_depths = trace_header(version=NEUROPIXEL_VERSION)['y']
252+
aligned_depths = h['y']
251253
depth_aligned, ind_aligned = np.unique(aligned_depths, return_index=True)
252254
depths, ind, iinv = np.unique(channels['localCoordinates'][:, 1], return_index=True, return_inverse=True)
253255
channels['mlapdv'] = np.zeros((nch, 3))

ibllib/atlas/atlas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ def xyz2ccf(self, xyz, ccf_order='mlapdv'):
781781
"""
782782
Converts coordinates to the CCF coordinates, which is assumed to be the cube indices
783783
times the spacing.
784-
:param xyz: mlapdv coordinates in um, origin Bregma
784+
:param xyz: mlapdv coordinates in meters, origin Bregma
785785
:param ccf_order: order that you want values returned 'mlapdv' (ibl) or 'apdvml'
786786
(Allen mcc vertices)
787787
:return: coordinates in CCF space um, origin is the front left top corner of the data

ibllib/dsp/voltage.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,37 @@ def fk(x, si=.002, dx=1, vbounds=None, btype='highpass', ntr_pad=0, ntr_tap=None
109109
return xf / gain
110110

111111

112+
def car(x, collection=None, lagc=300, butter_kwargs=None):
113+
"""
114+
Applies common average referencing with optional automatic gain control
115+
:param x: the input array to be filtered. dimension, the filtering is considering
116+
axis=0: spatial dimension, axis=1 temporal dimension. (ntraces, ns)
117+
:param collection:
118+
:param lagc: window size for time domain automatic gain control (no agc otherwise)
119+
:param butter_kwargs: filtering parameters: defaults: {'N': 3, 'Wn': 0.1, 'btype': 'highpass'}
120+
:return:
121+
"""
122+
if butter_kwargs is None:
123+
butter_kwargs = {'N': 3, 'Wn': 0.1, 'btype': 'highpass'}
124+
if collection is not None:
125+
xout = np.zeros_like(x)
126+
for c in np.unique(collection):
127+
sel = collection == c
128+
xout[sel, :] = kfilt(x=x[sel, :], ntr_pad=0, ntr_tap=None, collection=None,
129+
butter_kwargs=butter_kwargs)
130+
return xout
131+
132+
# apply agc and keep the gain in handy
133+
if not lagc:
134+
xf = np.copy(x)
135+
gain = 1
136+
else:
137+
xf, gain = agc(x, wl=lagc, si=1.0)
138+
# apply CAR and then un-apply the gain
139+
xf = xf - np.median(xf, axis=0)
140+
return xf / gain
141+
142+
112143
def kfilt(x, collection=None, ntr_pad=0, ntr_tap=None, lagc=300, butter_kwargs=None):
113144
"""
114145
Applies a butterworth filter on the 0-axis with tapering / padding
@@ -209,13 +240,19 @@ def destripe(x, fs, neuropixel_version=1, butter_kwargs=None, k_kwargs=None, cha
209240
True: deduces the bad channels from the data provided
210241
:param butter_kwargs: (optional, None) butterworth params, see the code for the defaults dict
211242
:param k_kwargs: (optional, None) K-filter params, see the code for the defaults dict
243+
can also be set to 'car', in which case the median accross channels will be subtracted
212244
:return: x, filtered array
213245
"""
214246
if butter_kwargs is None:
215247
butter_kwargs = {'N': 3, 'Wn': 300 / fs * 2, 'btype': 'highpass'}
216248
if k_kwargs is None:
217249
k_kwargs = {'ntr_pad': 60, 'ntr_tap': 0, 'lagc': 3000,
218250
'butter_kwargs': {'N': 3, 'Wn': 0.01, 'btype': 'highpass'}}
251+
spatial_fcn = lambda dat: kfilt(dat, **k_kwargs) # noqa
252+
elif isinstance(k_kwargs, dict):
253+
spatial_fcn = lambda dat: kfilt(dat, **k_kwargs) # noqa
254+
else:
255+
spatial_fcn = lambda dat: car(dat, lagc=int(0.1 * fs)) # noqa
219256
h = neuropixel.trace_header(version=neuropixel_version)
220257
if channel_labels is True:
221258
channel_labels, _ = detect_bad_channels(x, fs)
@@ -231,9 +268,9 @@ def destripe(x, fs, neuropixel_version=1, butter_kwargs=None, k_kwargs=None, cha
231268
if channel_labels is not None:
232269
x = interpolate_bad_channels(x, channel_labels, h)
233270
inside_brain = np.where(channel_labels != 3)[0]
234-
x[inside_brain, :] = kfilt(x[inside_brain, :], **k_kwargs) # apply the k-filter
271+
x[inside_brain, :] = spatial_fcn(x[inside_brain, :]) # apply the k-filter
235272
else:
236-
x = kfilt(x, **k_kwargs)
273+
x = spatial_fcn(x)
237274
return x
238275

239276

@@ -245,7 +282,7 @@ def decompress_destripe_cbin(sr_file, output_file=None, h=None, wrot=None, appen
245282
Production version with optimized FFTs - requires pyfftw
246283
:param sr: seismic reader object (spikeglx.Reader)
247284
:param output_file: (optional, defaults to .bin extension of the compressed bin file)
248-
:param h: (optional)
285+
:param h: (optional) neuropixel trace header. Dictionary with key 'sample_shift'
249286
:param wrot: (optional) whitening matrix [nc x nc] or amplitude scalar to apply to the output
250287
:param append: (optional, False) for chronic recordings, append to end of file
251288
:param nc_out: (optional, True) saves non selected channels (synchronisation trace) in output
@@ -273,7 +310,7 @@ def decompress_destripe_cbin(sr_file, output_file=None, h=None, wrot=None, appen
273310
k_kwargs = {'ntr_pad': 60, 'ntr_tap': 0, 'lagc': 3000,
274311
'butter_kwargs': {'N': 3, 'Wn': 0.01, 'btype': 'highpass'}}
275312
h = neuropixel.trace_header(version=1) if h is None else h
276-
ncv = h['x'].size # number of channels
313+
ncv = h['sample_shift'].size # number of channels
277314
output_file = sr.file_bin.with_suffix('.bin') if output_file is None else output_file
278315
assert output_file != sr.file_bin
279316
taper = np.r_[0, scipy.signal.windows.cosine((SAMPLES_TAPER - 1) * 2), 0]
@@ -504,7 +541,7 @@ def nxcor(x, ref):
504541
xcor = channels_similarity(raw)
505542
fscale, psd = scipy.signal.welch(raw * 1e6, fs=fs) # units; uV ** 2 / Hz
506543

507-
sos_hp = scipy.signal.butter(**{'N': 3, 'Wn': 1000 / fs / 2, 'btype': 'highpass'}, output='sos')
544+
sos_hp = scipy.signal.butter(**{'N': 3, 'Wn': 300 / fs * 2, 'btype': 'highpass'}, output='sos')
508545
hf = scipy.signal.sosfiltfilt(sos_hp, raw)
509546
xcorf = channels_similarity(hf)
510547

@@ -513,7 +550,7 @@ def nxcor(x, ref):
513550
'rms_raw': rms(raw), # very similar to the rms avfter butterworth filter
514551
'xcor_hf': detrend(xcor, 11),
515552
'xcor_lf': xcorf - detrend(xcorf, 11) - 1,
516-
'psd_hf': np.mean(psd[:, fscale > 12000], axis=-1),
553+
'psd_hf': np.mean(psd[:, fscale > (fs / 2 * 0.8)], axis=-1), # 80% nyquists
517554
})
518555

519556
# make recommendation

ibllib/io/extractors/camera.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ def groom_pin_state(gpio, audio, ts, tolerance=2., display=False, take='first',
553553
downs = ts[high2low] - ts[high2low][0]
554554
offsets = audio_times[1::2] - audio_times[1]
555555
assigned = attribute_times(offsets, downs, tol=tolerance, take=take)
556-
unassigned = np.setdiff1d(np.arange(onsets.size), assigned[assigned > -1])
556+
unassigned = np.setdiff1d(np.arange(offsets.size), assigned[assigned > -1])
557557
if unassigned.size > 0:
558558
_logger.debug(f'{unassigned.size} audio TTL falls were not detected by the camera')
559559
# Check that all pin state downticks could be attributed to an offset TTL

ibllib/io/spikeglx.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,11 @@ def _geometry_from_meta(meta_data):
478478
"""
479479
cm = _map_channels_from_meta(meta_data)
480480
major_version = _get_neuropixel_major_version_from_meta(meta_data)
481+
if cm is None:
482+
_logger.warning("Meta data doesn't have geometry (snsShankMap field), returning defaults")
483+
th = neuropixel.trace_header(version=major_version)
484+
th['flag'] = th['x'] * 0 + 1.
485+
return th
481486
th = cm.copy()
482487
if major_version == 1:
483488
# the spike sorting channel maps have a flipped version of the channel map

0 commit comments

Comments
 (0)