Skip to content

Commit 946e9b0

Browse files
authored
Merge pull request #33 from int-brain-lab/develop
Develop
2 parents edfe736 + e71c7a9 commit 946e9b0

26 files changed

+2964
-102
lines changed

atlaselectrophysiology/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ be lost once the line is deleted. The reference lines can be hidden/ redisplayed
130130
Once the alignment is complete, the new locations of the electrode locations can be uploaded to Alyx by pressing the **Upload**
131131
button or Shift + U.
132132

133+
Upon upload, a window will pop-up asking to determine 1) the confidence in alignment, 2) the QC (which will label the insertion), 3) the reason for that QC (e.g. Drift and Noise seen in the recording).
134+
<img width="294" alt="Capture d’écran 2021-03-19 à 11 04 04" src="https://user-images.githubusercontent.com/43007596/112831143-cdaa1480-9093-11eb-8fc2-12fd586e604b.png">
135+
136+
133137
### Additional Features
134138
#### Session Notes
135139
If any notes associated with a session have been uploaded to Alyx, these can be displayed as a popup by clicking on

atlaselectrophysiology/create_overview_plots.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import glob
44

55

6-
def make_overview_plot(folder, sess_info):
6+
def make_overview_plot(folder, sess_info, save_folder=None):
77

88
image_folder = folder
99
image_info = sess_info
10+
if not save_folder:
11+
save_folder = image_folder
1012

1113
def load_image(image_name, ax):
1214
with image_name as ifile:
@@ -74,6 +76,6 @@ def load_image(image_name, ax):
7476

7577
ax.text(0.5, 0, image_info[:-1], va="center", ha="center", transform=ax.transAxes)
7678

77-
plt.savefig(image_folder.joinpath(image_info + "overview.png"),
79+
plt.savefig(save_folder.joinpath(image_info + "overview.png"),
7880
bbox_inches='tight', pad_inches=0)
79-
plt.show()
81+
plt.show()

atlaselectrophysiology/ephys_atlas_gui.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,26 @@
1515

1616

1717
class MainWindow(QtWidgets.QMainWindow, ephys_gui.Setup):
18-
def __init__(self, offline=False):
18+
def __init__(self, offline=False, probe_id=None, one=None):
1919
super(MainWindow, self).__init__()
2020

2121
self.init_variables()
2222
self.init_layout(self, offline=offline)
23-
if not offline:
23+
self.configure = True
24+
if not offline and probe_id is None:
2425
self.loaddata = LoadData()
2526
self.populate_lists(self.loaddata.get_subjects(), self.subj_list, self.subj_combobox)
27+
elif not offline and probe_id is not None:
28+
self.loaddata = LoadData(probe_id=probe_id, one=one)
29+
self.loaddata.get_info(0)
30+
self.feature_prev, self.track_prev = self.loaddata.get_starting_alignment(0)
31+
self.data_status=False
32+
self.data_button_pressed()
2633
else:
2734
self.loaddata = LoadDataLocal()
35+
2836
self.allen = self.loaddata.get_allen_csv()
2937
self.init_region_lookup(self.allen)
30-
self.configure = True
3138

3239
def init_variables(self):
3340
"""
@@ -321,7 +328,7 @@ def set_view(self, view=1, configure=False):
321328
self.fig_line.update()
322329
self.fig_probe.update()
323330

324-
def save_plots(self):
331+
def save_plots(self, save_path=None):
325332
"""
326333
Saves all plots from the GUI into folder
327334
"""
@@ -336,6 +343,9 @@ def save_plots(self):
336343
image_path_overview = self.alf_path.joinpath('GUI_plots')
337344
image_path = image_path_overview
338345

346+
if save_path:
347+
image_path_overview = Path(save_path)
348+
339349
os.makedirs(image_path_overview, exist_ok=True)
340350
os.makedirs(image_path, exist_ok=True)
341351
# Reset all axis, put view back to 1 and remove any reference lines
@@ -490,7 +500,7 @@ def save_plots(self):
490500
self.set_font(self.fig_hist_ref, 'bottom', ptsize=8)
491501
self.set_axis(self.fig_hist_ref, 'bottom', pen='w')
492502

493-
make_overview_plot(image_path, sess_info)
503+
make_overview_plot(image_path, sess_info, save_folder=image_path_overview)
494504

495505
def toggle_plots(self, options_group):
496506
"""
@@ -1130,6 +1140,7 @@ def data_button_pressed(self):
11301140
self.line_fr_data, self.line_amp_data = self.plotdata.get_fr_amp_data_line()
11311141
self.probe_rfmap, self.rfmap_boundaries = self.plotdata.get_rfmap_data()
11321142
self.img_stim_data = self.plotdata.get_passive_events()
1143+
11331144
self.slice_data = self.loaddata.get_slice_images(self.ephysalign.xyz_samples)
11341145

11351146
self.data_status = True
@@ -1546,10 +1557,10 @@ def complete_button_pressed_offline(self):
15461557
QtGui.QMessageBox.information(self, 'Status', "Channels locations saved")
15471558
else:
15481559
pass
1549-
QtGui.QMessageBox.information(self, 'Status', "Channels not saved")
1560+
QtGui.QMessageBox.warning(self, 'Status', "Channels not saved")
15501561

15511562
def display_qc_options(self):
1552-
self.qc_dialog.exec()
1563+
self.qc_dialog.open()
15531564

15541565
def qc_button_clicked(self):
15551566
align_qc = self.align_qc.currentText()
@@ -1559,6 +1570,11 @@ def qc_button_clicked(self):
15591570
if button.isChecked():
15601571
ephys_desc.append(button.text())
15611572

1573+
if ephys_qc != 'Pass' and len(ephys_desc) == 0:
1574+
QtGui.QMessageBox.warning(self, 'Status', "You must select a reason for qc choice")
1575+
self.display_qc_options()
1576+
return
1577+
15621578
self.loaddata.upload_dj(align_qc, ephys_qc, ephys_desc)
15631579
self.complete_button_pressed()
15641580

@@ -1868,16 +1884,24 @@ def update_string(self):
18681884
self.tot_idx_string.setText(f"Total Index = {self.total_idx}")
18691885

18701886

1887+
def viewer(probe_id=None, one=None):
1888+
# To generate the plot from the command line
1889+
mainapp = MainWindow(probe_id=probe_id, one=one)
1890+
mainapp.show()
1891+
return mainapp
1892+
1893+
18711894
if __name__ == '__main__':
18721895

18731896
import argparse
18741897

18751898
parser = argparse.ArgumentParser(description='Offline vs online mode')
18761899
parser.add_argument('-o', '--offline', default=False, required=False, help='Offline mode')
1900+
parser.add_argument('-i', '--insertion', default=None, required=False, help='Insertion mode')
18771901
args = parser.parse_args()
18781902

18791903
app = QtWidgets.QApplication([])
1880-
mainapp = MainWindow(offline=args.offline)
1904+
mainapp = MainWindow(offline=args.offline, probe_id=args.insertion)
18811905
# mainapp = MainWindow(offline=True)
18821906
mainapp.show()
18831907
app.exec_()

atlaselectrophysiology/ephys_gui_setup.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,12 @@ def init_menubar(self):
201201
# Define unit filtering options
202202
all_units = QtGui.QAction('All', self, checkable=True, checked=True)
203203
all_units.triggered.connect(lambda: self.filter_unit_pressed('all'))
204-
good_units = QtGui.QAction('Good', self, checkable=True, checked=False)
205-
good_units.triggered.connect(lambda: self.filter_unit_pressed('good'))
206-
mua_units = QtGui.QAction('MUA', self, checkable=True, checked=False)
207-
mua_units.triggered.connect(lambda: self.filter_unit_pressed('mua'))
204+
good_units = QtGui.QAction('KS good', self, checkable=True, checked=False)
205+
good_units.triggered.connect(lambda: self.filter_unit_pressed('KS good'))
206+
mua_units = QtGui.QAction('KS mua', self, checkable=True, checked=False)
207+
mua_units.triggered.connect(lambda: self.filter_unit_pressed('KS mua'))
208+
ibl_units = QtGui.QAction('IBL good', self, checkable=True, checked=False)
209+
ibl_units.triggered.connect(lambda: self.filter_unit_pressed('IBL good'))
208210
# Initialise with all units being shown
209211
self.unit_init = all_units
210212

@@ -219,6 +221,8 @@ def init_menubar(self):
219221
unit_filter_options_group.addAction(good_units)
220222
unit_filter_options.addAction(mua_units)
221223
unit_filter_options_group.addAction(mua_units)
224+
unit_filter_options.addAction(ibl_units)
225+
unit_filter_options_group.addAction(ibl_units)
222226

223227
# FIT OPTIONS MENU BAR
224228
# Define all possible keyboard shortcut interactions for GUI

atlaselectrophysiology/load_data.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def __init__(self, one=None, brain_atlas=None, testing=False, probe_id=None):
5151
self.resolved = None
5252
self.alyx_str = None
5353

54+
if probe_id is not None:
55+
self.sess = self.one.alyx.rest('trajectories', 'list', provenance='Histology track',
56+
probe_insertion=probe_id)
57+
5458
def get_subjects(self):
5559
"""
5660
Finds all subjects that have a histology track trajectory registered
@@ -87,8 +91,8 @@ def get_sessions(self, idx):
8791
:return session: list of sessions associated with subject, displayed as date + probe
8892
:type: list of strings
8993
"""
90-
self.subj = self.subjects[idx]
91-
sess_idx = [i for i, e in enumerate(self.subj_with_hist) if e == self.subj]
94+
subj = self.subjects[idx]
95+
sess_idx = [i for i, e in enumerate(self.subj_with_hist) if e == subj]
9296
self.sess = [self.sess_with_hist[idx] for idx in sess_idx]
9397
session = [(sess['session']['start_time'][:10] + ' ' + sess['probe_name']) for sess in
9498
self.sess]
@@ -111,6 +115,7 @@ def get_info(self, idx):
111115
self.probe_id = self.sess[idx]['probe_insertion']
112116
self.lab = self.sess[idx]['session']['lab']
113117
self.eid = self.sess[idx]['session']['id']
118+
self.subj = self.sess[idx]['session']['subject']
114119

115120
return self.get_previous_alignments()
116121

@@ -215,16 +220,21 @@ def get_data(self):
215220
'_iblqc_ephysSpectralDensity.amps',
216221
'_ibl_passiveGabor.table',
217222
'_ibl_passivePeriods.intervalsTable',
218-
'_ibl_passiveRFM.frames',
223+
'_iblrig_RFMapStim.raw',
219224
'_ibl_passiveRFM.times',
220-
'_ibl_passiveStims.table'
225+
'_ibl_passiveStims.table',
226+
'_iblrig_RFMapStim.raw'
221227
]
222228

223229
print(self.subj)
224230
print(self.probe_label)
225231
print(self.date)
226232
print(self.eid)
227233

234+
# dsets = self.one.alyx.rest('datasets', 'list', probe_insertion=self.probe_id)
235+
# dsets_int = [d for d in dsets if d['dataset_type'] in dtypes]
236+
# _ = self.one.download_datasets(dsets_int)
237+
228238
_ = self.one.load(self.eid, dataset_types=dtypes, download_only=True)
229239
self.sess_path = self.one.path_from_eid(self.eid)
230240

atlaselectrophysiology/plot_data.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44
import alf.io
55
from brainbox.processing import bincount2D
6-
from brainbox.population import xcorr
6+
from brainbox.population.decode import xcorr
77
from brainbox.task import passive
88
import scipy
99
from PyQt5 import QtGui
@@ -58,8 +58,19 @@ def __init__(self, alf_path, ephys_path):
5858
self.lfp_data_status = False
5959

6060
try:
61-
self.rf_map = alf.io.load_object(self.alf_path.parent, object='passiveRFM',
62-
namespace='ibl')
61+
rf_map_times = alf.io.load_object(self.alf_path.parent, object='passiveRFM',
62+
namespace='ibl')
63+
# This needs to go into brainbox!!
64+
rf_map_frames_path = (self.alf_path.parent.parent.
65+
joinpath('raw_passive_data', '_iblrig_RFMapStim.raw.bin'))
66+
rf_map_frames = np.fromfile(rf_map_frames_path, dtype="uint8")
67+
y_pix, x_pix = 15, 15
68+
frames = np.transpose(np.reshape(rf_map_frames, [y_pix, x_pix, -1], order="F"),
69+
[2, 1, 0])
70+
71+
self.rf_map = dict()
72+
self.rf_map['times'] = rf_map_times['times']
73+
self.rf_map['frames'] = frames
6374
if len(self.rf_map) == 2:
6475
self.rfmap_data_status = True
6576
else:
@@ -94,15 +105,27 @@ def __init__(self, alf_path, ephys_path):
94105
def filter_units(self, type):
95106
if type == 'all':
96107
self.spike_idx = np.arange(self.spikes['clusters'].size)
97-
# Filter for nans in depths and also in amps
98-
self.kp_idx = np.where(~np.isnan(self.spikes['depths'][self.spike_idx]) &
99-
~np.isnan(self.spikes['amps'][self.spike_idx]))[0]
100108

101-
else:
102-
clust = np.where(self.clusters.metrics.ks2_label == type)
109+
elif type == 'KS good':
110+
clust = np.where(self.clusters.metrics.ks2_label == 'good')
111+
self.spike_idx = np.where(np.isin(self.spikes['clusters'], clust))[0]
112+
113+
elif type == 'KS mua':
114+
clust = np.where(self.clusters.metrics.ks2_label == 'mua')
103115
self.spike_idx = np.where(np.isin(self.spikes['clusters'], clust))[0]
104-
self.kp_idx = np.where(~np.isnan(self.spikes['depths'][self.spike_idx]) & ~np.isnan(
105-
self.spikes['amps'][self.spike_idx]))[0]
116+
117+
elif type == 'IBL good':
118+
try:
119+
clust = np.where(self.clusters.metrics.label == 1)
120+
self.spike_idx = np.where(np.isin(self.spikes['clusters'], clust))[0]
121+
except Exception:
122+
print('IBL metrics not implemented will return ks good units instead')
123+
clust = np.where(self.clusters.metrics.ks2_label == 'good')
124+
self.spike_idx = np.where(np.isin(self.spikes['clusters'], clust))[0]
125+
126+
# Filter for nans in depths and also in amps
127+
self.kp_idx = np.where(~np.isnan(self.spikes['depths'][self.spike_idx]) &
128+
~np.isnan(self.spikes['amps'][self.spike_idx]))[0]
106129

107130
# Plots that require spike and cluster data
108131
def get_depth_data_scatter(self):
@@ -477,7 +500,7 @@ def get_rfmap_data(self):
477500
(rf_map_times, rf_map_pos,
478501
rf_stim_frames) = passive.get_on_off_times_and_positions(self.rf_map)
479502

480-
rf_map, depths = \
503+
rf_map, _ = \
481504
passive.get_rf_map_over_depth(rf_map_times, rf_map_pos, rf_stim_frames,
482505
self.spikes['times'][self.spike_idx][self.kp_idx],
483506
self.spikes['depths'][self.spike_idx][self.kp_idx],
@@ -491,6 +514,8 @@ def get_rfmap_data(self):
491514
xscale = 1
492515
levels = np.quantile(np.c_[img['on'], img['off']], [0, 1])
493516

517+
depths = np.linspace(0, 3840, len(rfs_svd['on']) + 1)
518+
494519
sub_type = ['on', 'off']
495520
for sub in sub_type:
496521
sub_data = {sub: {

atlasview/atlasview.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def replace_image_layer(self, index, **kwargs):
208208

209209
class PgImageController:
210210
"""
211-
Abstract class that implements mapping from axes to voxels for any window.
211+
Abstract class that implements mapping fr`om axes to voxels for any window.
212212
Not instantiated directly.
213213
"""
214214
def __init__(self, win, res=25):

0 commit comments

Comments
 (0)