Skip to content

Commit 364f80e

Browse files
authored
Merge pull request #69 from ttngu207/no-curation
Add no-curation version and run kilosort analysis
2 parents 7a4fba9 + ddd4095 commit 364f80e

File tree

12 files changed

+2235
-216
lines changed

12 files changed

+2235
-216
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
44

55

6+
## [0.1.0b4] - 2021-11-29
7+
### Added
8+
+ Processing with Kilosort and pyKilosort for Open Ephys and SpikeGLX
9+
10+
611
## [0.1.0b0] - 2021-05-07
712
### Added
813
+ First beta release

element_array_ephys/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1-
# ephys_acute as default
2-
import element_array_ephys.ephys_acute as ephys
1+
import datajoint as dj
2+
import logging
3+
import os
4+
5+
6+
dj.config['enable_python_native_blobs'] = True
7+
8+
9+
def get_logger(name):
10+
log = logging.getLogger(name)
11+
log.setLevel(os.getenv('LOGLEVEL', 'INFO'))
12+
return log
13+
14+
from . import ephys_acute as ephys

element_array_ephys/ephys_acute.py

Lines changed: 316 additions & 60 deletions
Large diffs are not rendered by default.

element_array_ephys/ephys_chronic.py

Lines changed: 251 additions & 52 deletions
Large diffs are not rendered by default.

element_array_ephys/ephys_no_curation.py

Lines changed: 979 additions & 0 deletions
Large diffs are not rendered by default.

element_array_ephys/probe.py

Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def activate(schema_name, *, create_schema=True, create_tables=True):
1818
schema.activate(schema_name, create_schema=create_schema, create_tables=create_tables)
1919

2020
# Add neuropixels probes
21-
for probe_type in ('neuropixels 1.0 - 3A', 'neuropixels 1.0 - 3B',
21+
for probe_type in ('neuropixels 1.0 - 3A', 'neuropixels 1.0 - 3B', 'neuropixels UHD',
2222
'neuropixels 2.0 - SS', 'neuropixels 2.0 - MS'):
2323
ProbeType.create_neuropixels_probe(probe_type)
2424

@@ -46,15 +46,38 @@ class Electrode(dj.Part):
4646
def create_neuropixels_probe(probe_type='neuropixels 1.0 - 3A'):
4747
"""
4848
Create `ProbeType` and `Electrode` for neuropixels probes:
49-
1.0 (3A and 3B), 2.0 (SS and MS)
49+
+ neuropixels 1.0 - 3A
50+
+ neuropixels 1.0 - 3B
51+
+ neuropixels UHD
52+
+ neuropixels 2.0 - SS
53+
+ neuropixels 2.0 - MS
54+
5055
For electrode location, the (0, 0) is the
5156
bottom left corner of the probe (ignore the tip portion)
5257
Electrode numbering is 1-indexing
5358
"""
5459

60+
neuropixels_probes_config = {
61+
'neuropixels 1.0 - 3A': dict(site_count=960, col_spacing=32, row_spacing=20,
62+
white_spacing=16, col_count=2,
63+
shank_count=1, shank_spacing=0),
64+
'neuropixels 1.0 - 3B': dict(site_count=960, col_spacing=32, row_spacing=20,
65+
white_spacing=16, col_count=2,
66+
shank_count=1, shank_spacing=0),
67+
'neuropixels UHD': dict(site_count=384, col_spacing=6, row_spacing=6,
68+
white_spacing=0, col_count=8,
69+
shank_count=1, shank_spacing=0),
70+
'neuropixels 2.0 - SS': dict(site_count=1280, col_spacing=32, row_spacing=15,
71+
white_spacing=0, col_count=2,
72+
shank_count=1, shank_spacing=250),
73+
'neuropixels 2.0 - MS': dict(site_count=1280, col_spacing=32, row_spacing=15,
74+
white_spacing=0, col_count=2,
75+
shank_count=4, shank_spacing=250)
76+
}
77+
5578
def build_electrodes(site_count, col_spacing, row_spacing,
56-
white_spacing, col_count=2,
57-
shank_count=1, shank_spacing=250):
79+
white_spacing, col_count,
80+
shank_count, shank_spacing):
5881
"""
5982
:param site_count: site count per shank
6083
:param col_spacing: (um) horrizontal spacing between sites
@@ -66,14 +89,15 @@ def build_electrodes(site_count, col_spacing, row_spacing,
6689
:return:
6790
"""
6891
row_count = int(site_count / col_count)
69-
x_coords = np.tile([0, 0 + col_spacing], row_count)
70-
x_white_spaces = np.tile([white_spacing, white_spacing, 0, 0], int(row_count / 2))
92+
x_coords = np.tile(np.arange(0, col_spacing * col_count, col_spacing), row_count)
93+
y_coords = np.repeat(np.arange(row_count) * row_spacing, col_count)
7194

72-
x_coords = x_coords + x_white_spaces
73-
y_coords = np.repeat(np.arange(row_count) * row_spacing, 2)
95+
if white_spacing:
96+
x_white_spaces = np.tile([white_spacing, white_spacing, 0, 0], int(row_count / 2))
97+
x_coords = x_coords + x_white_spaces
7498

75-
shank_cols = np.tile([0, 1], row_count)
76-
shank_rows = np.repeat(range(row_count), 2)
99+
shank_cols = np.tile(range(col_count), row_count)
100+
shank_rows = np.repeat(range(row_count), col_count)
77101

78102
npx_electrodes = []
79103
for shank_no in range(shank_count):
@@ -88,51 +112,12 @@ def build_electrodes(site_count, col_spacing, row_spacing,
88112

89113
return npx_electrodes
90114

91-
# ---- 1.0 3A ----
92-
if probe_type == 'neuropixels 1.0 - 3A':
93-
electrodes = build_electrodes(site_count=960, col_spacing=32, row_spacing=20,
94-
white_spacing=16, col_count=2)
95-
96-
probe_type = {'probe_type': 'neuropixels 1.0 - 3A'}
97-
with ProbeType.connection.transaction:
98-
ProbeType.insert1(probe_type, skip_duplicates=True)
99-
ProbeType.Electrode.insert([{**probe_type, **e} for e in electrodes],
100-
skip_duplicates=True)
101-
102-
# ---- 1.0 3B ----
103-
if probe_type == 'neuropixels 1.0 - 3B':
104-
electrodes = build_electrodes(site_count=960, col_spacing=32, row_spacing=20,
105-
white_spacing=16, col_count=2)
106-
107-
probe_type = {'probe_type': 'neuropixels 1.0 - 3B'}
108-
with ProbeType.connection.transaction:
109-
ProbeType.insert1(probe_type, skip_duplicates=True)
110-
ProbeType.Electrode.insert([{**probe_type, **e} for e in electrodes],
111-
skip_duplicates=True)
112-
113-
# ---- 2.0 Single shank ----
114-
if probe_type == 'neuropixels 2.0 - SS':
115-
electrodes = build_electrodes(site_count=1280, col_spacing=32, row_spacing=15,
116-
white_spacing=0, col_count=2,
117-
shank_count=1, shank_spacing=250)
118-
119-
probe_type = {'probe_type': 'neuropixels 2.0 - SS'}
120-
with ProbeType.connection.transaction:
121-
ProbeType.insert1(probe_type, skip_duplicates=True)
122-
ProbeType.Electrode.insert([{**probe_type, **e} for e in electrodes],
123-
skip_duplicates=True)
124-
125-
# ---- 2.0 Multi shank ----
126-
if probe_type == 'neuropixels 2.0 - MS':
127-
electrodes = build_electrodes(site_count=1280, col_spacing=32, row_spacing=15,
128-
white_spacing=0, col_count=2,
129-
shank_count=4, shank_spacing=250)
130-
131-
probe_type = {'probe_type': 'neuropixels 2.0 - MS'}
132-
with ProbeType.connection.transaction:
133-
ProbeType.insert1(probe_type, skip_duplicates=True)
134-
ProbeType.Electrode.insert([{**probe_type, **e} for e in electrodes],
135-
skip_duplicates=True)
115+
electrodes = build_electrodes(**neuropixels_probes_config[probe_type])
116+
probe_type = {'probe_type': probe_type}
117+
with ProbeType.connection.transaction:
118+
ProbeType.insert1(probe_type, skip_duplicates=True)
119+
ProbeType.Electrode.insert([{**probe_type, **e} for e in electrodes],
120+
skip_duplicates=True)
136121

137122

138123
@schema

element_array_ephys/readers/kilosort.py

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

1313
class Kilosort:
1414

15-
kilosort_files = [
15+
_kilosort_core_files = [
1616
'params.py',
1717
'amplitudes.npy',
1818
'channel_map.npy',
@@ -22,47 +22,60 @@ class Kilosort:
2222
'similar_templates.npy',
2323
'spike_templates.npy',
2424
'spike_times.npy',
25-
'spike_times_sec.npy',
26-
'spike_times_sec_adj.npy',
2725
'template_features.npy',
2826
'template_feature_ind.npy',
2927
'templates.npy',
3028
'templates_ind.npy',
3129
'whitening_mat.npy',
3230
'whitening_mat_inv.npy',
33-
'spike_clusters.npy',
31+
'spike_clusters.npy'
32+
]
33+
34+
_kilosort_additional_files = [
35+
'spike_times_sec.npy',
36+
'spike_times_sec_adj.npy',
3437
'cluster_groups.csv',
3538
'cluster_KSLabel.tsv'
3639
]
3740

38-
# keys to self.files, .data are file name e.g. self.data['params'], etc.
39-
kilosort_keys = [path.splitext(kilosort_file)[0] for kilosort_file in kilosort_files]
41+
kilosort_files = _kilosort_core_files + _kilosort_additional_files
4042

4143
def __init__(self, kilosort_dir):
4244
self._kilosort_dir = pathlib.Path(kilosort_dir)
4345
self._files = {}
4446
self._data = None
4547
self._clusters = None
4648

47-
params_filepath = kilosort_dir / 'params.py'
48-
49-
if not params_filepath.exists():
50-
raise FileNotFoundError(f'No Kilosort output found in: {kilosort_dir}')
49+
self.validate()
5150

51+
params_filepath = kilosort_dir / 'params.py'
5252
self._info = {'time_created': datetime.fromtimestamp(params_filepath.stat().st_ctime),
5353
'time_modified': datetime.fromtimestamp(params_filepath.stat().st_mtime)}
5454

5555
@property
5656
def data(self):
5757
if self._data is None:
58-
self._stat()
58+
self._load()
5959
return self._data
6060

6161
@property
6262
def info(self):
6363
return self._info
6464

65-
def _stat(self):
65+
def validate(self):
66+
"""
67+
Check if this is a valid set of kilosort outputs - i.e. all crucial files exist
68+
"""
69+
missing_files = []
70+
for f in Kilosort._kilosort_core_files:
71+
full_path = self._kilosort_dir / f
72+
if not full_path.exists():
73+
missing_files.append(f)
74+
if missing_files:
75+
raise FileNotFoundError(f'Kilosort files missing in ({self._kilosort_dir}):'
76+
f' {missing_files}')
77+
78+
def _load(self):
6679
self._data = {}
6780
for kilosort_filename in Kilosort.kilosort_files:
6881
kilosort_filepath = self._kilosort_dir / kilosort_filename
@@ -91,16 +104,18 @@ def _stat(self):
91104
self._data[base] = (np.reshape(d, d.shape[0])
92105
if d.ndim == 2 and d.shape[1] == 1 else d)
93106

107+
self._data['channel_map'] = self._data['channel_map'].flatten()
108+
94109
# Read the Cluster Groups
95-
for cluster_pattern, cluster_col_name in zip(['cluster_groups.*', 'cluster_KSLabel.*'],
110+
for cluster_pattern, cluster_col_name in zip(['cluster_group.*', 'cluster_KSLabel.*'],
96111
['group', 'KSLabel']):
97112
try:
98113
cluster_file = next(self._kilosort_dir.glob(cluster_pattern))
99114
except StopIteration:
100115
pass
101116
else:
102117
cluster_file_suffix = cluster_file.suffix
103-
assert cluster_file_suffix in ('.csv', '.tsv', '.xlsx')
118+
assert cluster_file_suffix in ('.tsv', '.xlsx')
104119
break
105120
else:
106121
raise FileNotFoundError(

0 commit comments

Comments
 (0)