Skip to content

Commit 3866aa6

Browse files
authored
Merge pull request #28 from int-brain-lab/alignment_dev
Alignment dev
2 parents 49d4122 + 472d307 commit 3866aa6

File tree

7 files changed

+292
-34
lines changed

7 files changed

+292
-34
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ exclude =
88
data_exploration_gui/
99
launch_phy/
1010
histology/
11-
11+
qt_matplotlib.py

atlaselectrophysiology/ephys_atlas_gui.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def init_variables(self):
8181
self.scale_regions = np.empty((0, 1))
8282
self.slice_lines = []
8383
self.slice_items = []
84+
self.probe_bounds = []
8485

8586
# Variables to keep track of popup plots
8687
self.cluster_popups = []
@@ -905,7 +906,7 @@ def plot_line(self, data):
905906
self.set_axis(self.fig_line, 'bottom', label=data['xaxis'])
906907
self.line_plots.append(line)
907908

908-
def plot_probe(self, data):
909+
def plot_probe(self, data, bounds=None):
909910
"""
910911
Plots a 2D image with probe geometry
911912
param data: dictionary of data to plot
@@ -925,9 +926,11 @@ def plot_probe(self, data):
925926
else:
926927
[self.fig_probe.removeItem(plot) for plot in self.probe_plots]
927928
[self.fig_probe_cb.removeItem(cbar) for cbar in self.probe_cbars]
929+
[self.fig_probe.removeItem(line) for line in self.probe_bounds]
928930
self.set_axis(self.fig_probe_cb, 'top', pen='w')
929931
self.probe_plots = []
930932
self.probe_cbars = []
933+
self.probe_bounds = []
931934
color_bar = cb.ColorBar(data['cmap'])
932935
lut = color_bar.getColourMap()
933936
for img, scale, offset in zip(data['img'], data['scale'], data['offset']):
@@ -936,18 +939,24 @@ def plot_probe(self, data):
936939
image.translate(offset[0], offset[1])
937940
image.scale(scale[0], scale[1])
938941
image.setLookupTable(lut)
939-
image.setLevels((data['level'][0], data['level'][1]))
942+
image.setLevels((data['levels'][0], data['levels'][1]))
940943
self.fig_probe.addItem(image)
941944
self.probe_plots.append(image)
942945

943-
cbar = color_bar.makeColourBar(20, 5, self.fig_probe_cb, min=data['level'][0],
944-
max=data['level'][1], label=data['title'], lim=True)
946+
cbar = color_bar.makeColourBar(20, 5, self.fig_probe_cb, min=data['levels'][0],
947+
max=data['levels'][1], label=data['title'], lim=True)
945948
self.fig_probe_cb.addItem(cbar)
946949
self.probe_cbars.append(cbar)
947950

948951
self.fig_probe.setXRange(min=data['xrange'][0], max=data['xrange'][1], padding=0)
949952
self.fig_probe.setYRange(min=self.probe_tip - self.probe_extra,
950953
max=self.probe_top + self.probe_extra, padding=self.pad)
954+
if bounds is not None:
955+
# add some infinite line stuff
956+
for bound in bounds:
957+
line = pg.InfiniteLine(pos=bound, angle=0, pen='w')
958+
self.fig_probe.addItem(line)
959+
self.probe_bounds.append(line)
951960

952961
def plot_image(self, data):
953962
"""
@@ -975,6 +984,7 @@ def plot_image(self, data):
975984

976985
image = pg.ImageItem()
977986
image.setImage(data['img'])
987+
image.translate(data['offset'][0], data['offset'][1])
978988
image.scale(data['scale'][0], data['scale'][1])
979989
cmap = data.get('cmap', [])
980990
if cmap:
@@ -1107,10 +1117,14 @@ def data_button_pressed(self):
11071117
'LF')
11081118
self.img_lfp_data, self.probe_lfp_data = self.plotdata.get_lfp_spectrum_data()
11091119
self.line_fr_data, self.line_amp_data = self.plotdata.get_fr_amp_data_line()
1120+
self.probe_rfmap, self.rfmap_boundaries = self.plotdata.get_rfmap_data()
1121+
self.img_stim_data = self.plotdata.get_passive_events()
11101122
self.slice_data = self.loaddata.get_slice_images(self.ephysalign.xyz_samples)
11111123

11121124
self.data_status = True
11131125

1126+
self.init_menubar()
1127+
11141128
# Initialise checked plots
11151129
self.img_init.setChecked(True)
11161130
self.line_init.setChecked(True)
@@ -1174,6 +1188,8 @@ def filter_unit_pressed(self, type):
11741188
self.img_corr_data = self.plotdata.get_correlation_data_img()
11751189
self.img_fr_data = self.plotdata.get_fr_img()
11761190
self.line_fr_data, self.line_amp_data = self.plotdata.get_fr_amp_data_line()
1191+
self.probe_rfmap, self.rfmap_boundaries = self.plotdata.get_rfmap_data()
1192+
self.img_stim_data = self.plotdata.get_passive_events()
11771193
self.img_init.setChecked(True)
11781194
self.line_init.setChecked(True)
11791195
self.probe_init.setChecked(True)
@@ -1527,7 +1543,11 @@ def display_qc_options(self):
15271543
def qc_button_clicked(self):
15281544
align_qc = self.align_qc.currentText()
15291545
ephys_qc = self.ephys_qc.currentText()
1530-
ephys_desc = self.ephys_desc.currentText()
1546+
ephys_desc = []
1547+
for button in self.desc_buttons.buttons():
1548+
if button.isChecked():
1549+
ephys_desc.append(button.text())
1550+
15311551
self.loaddata.upload_dj(align_qc, ephys_qc, ephys_desc)
15321552
self.complete_button_pressed()
15331553

atlaselectrophysiology/ephys_gui_setup.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def init_layout(self, main_window, offline=False):
1616
main_widget = QtWidgets.QWidget()
1717
self.setCentralWidget(main_widget)
1818

19-
self.init_menubar()
2019
self.init_interaction_features()
2120
self.init_figures()
2221

@@ -74,6 +73,7 @@ def init_menubar(self):
7473
img_rmsLFP.triggered.connect(lambda: self.plot_image(self.img_rms_LFPdata))
7574
img_LFP = QtGui.QAction('LFP Spectrum', self, checkable=True, checked=False)
7675
img_LFP.triggered.connect(lambda: self.plot_image(self.img_lfp_data))
76+
7777
# Initialise with firing rate 2D plot
7878
self.img_init = img_fr
7979

@@ -102,6 +102,14 @@ def init_menubar(self):
102102
img_options.addAction(scatter_amp)
103103
self.img_options_group.addAction(scatter_amp)
104104

105+
stim_type = list(self.img_stim_data.keys())
106+
for stim in stim_type:
107+
img = QtGui.QAction(stim, self, checkable=True, checked=False)
108+
img.triggered.connect(lambda checked, item=stim: self.plot_image(
109+
self.img_stim_data[item]))
110+
img_options.addAction(img)
111+
self.img_options_group.addAction(img)
112+
105113
# LINE PLOTS MENU BAR
106114
# Define all 1D line plot options
107115
line_fr = QtGui.QAction('Firing Rate', self, checkable=True, checked=True)
@@ -128,6 +136,7 @@ def init_menubar(self):
128136
probe_rmsAP.triggered.connect(lambda: self.plot_probe(self.probe_rms_APdata))
129137
probe_rmsLFP = QtGui.QAction('rms LFP', self, checkable=True, checked=False)
130138
probe_rmsLFP.triggered.connect(lambda: self.plot_probe(self.probe_rms_LFPdata))
139+
131140
# Initialise with rms of AP probe plot
132141
self.probe_init = probe_rmsAP
133142

@@ -152,6 +161,14 @@ def init_menubar(self):
152161
probe_options.addAction(probe)
153162
self.probe_options_group.addAction(probe)
154163

164+
sub_types = list(self.probe_rfmap.keys())
165+
for sub in sub_types:
166+
probe = QtGui.QAction(f'RF Map - {sub}', self, checkable=True, checked=False)
167+
probe.triggered.connect(lambda checked, item=sub: self.plot_probe(
168+
self.probe_rfmap[item], bounds=self.rfmap_boundaries))
169+
probe_options.addAction(probe)
170+
self.probe_options_group.addAction(probe)
171+
155172
# SLICE PLOTS MENU BAR
156173
# Define all coronal slice plot options
157174
slice_hist_rd = QtGui.QAction('Histology Red', self, checkable=True, checked=True)
@@ -462,10 +479,23 @@ def init_interaction_features(self):
462479
ephys_qc_label = QtGui.QLabel("QC for ephys recording")
463480
self.ephys_qc = QtGui.QComboBox()
464481
self.ephys_qc.addItems(["Pass", "Warning", "Critical"])
465-
ephys_desc_label = QtGui.QLabel("Describe problem with recording")
466-
self.ephys_desc = QtGui.QComboBox()
467-
self.ephys_desc.addItems(["None", "Noise and artifact", "Drift", "Poor neural yield",
468-
"Brain Damage", "Other"])
482+
483+
self.desc_buttons = QtWidgets.QButtonGroup()
484+
self.desc_group = QtWidgets.QGroupBox("Describe problem with recording")
485+
self.desc_layout = QtWidgets.QVBoxLayout()
486+
self.desc_layout.setSpacing(5)
487+
self.desc_buttons.setExclusive(False)
488+
options = ["Noise and artifact", "Drift", "Poor neural yield", "Brain Damage", "Other"]
489+
for i, val in enumerate(options):
490+
491+
button = QtWidgets.QCheckBox(val)
492+
button.setCheckState(QtCore.Qt.Unchecked)
493+
494+
self.desc_buttons.addButton(button, id=i)
495+
self.desc_layout.addWidget(button)
496+
497+
self.desc_group.setLayout(self.desc_layout)
498+
469499
self.qc_dialog = QtGui.QDialog(self)
470500
self.qc_dialog.setWindowTitle('QC assessment')
471501
self.qc_dialog.resize(300, 150)
@@ -480,8 +510,7 @@ def init_interaction_features(self):
480510
dialog_layout.addWidget(self.align_qc)
481511
dialog_layout.addWidget(ephys_qc_label)
482512
dialog_layout.addWidget(self.ephys_qc)
483-
dialog_layout.addWidget(ephys_desc_label)
484-
dialog_layout.addWidget(self.ephys_desc)
513+
dialog_layout.addWidget(self.desc_group)
485514
dialog_layout.addWidget(buttonBox)
486515
self.qc_dialog.setLayout(dialog_layout)
487516

@@ -727,3 +756,17 @@ def closeEvent(self, event):
727756

728757
def leaveEvent(self, event):
729758
self.moved.emit()
759+
760+
761+
class CheckableComboBox(QtGui.QComboBox):
762+
def __init__(self):
763+
super(CheckableComboBox, self).__init__()
764+
self.view().pressed.connect(self.handleItemPressed)
765+
self.setModel(QtGui.QStandardItemModel(self))
766+
767+
def handleItemPressed(self, index):
768+
item = self.model().itemFromIndex(index)
769+
if item.checkState() == QtCore.Qt.Checked:
770+
item.setCheckState(QtCore.Qt.Unchecked)
771+
else:
772+
item.setCheckState(QtCore.Qt.Checked)

atlaselectrophysiology/load_data.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99
import alf.io
1010
import glob
11+
import os
1112
from atlaselectrophysiology.load_histology import download_histology_data, tif2nrrd
1213

1314
ONE_BASE_URL = "https://alyx.internationalbrainlab.org"
@@ -48,6 +49,7 @@ def __init__(self, one=None, brain_atlas=None, testing=False, probe_id=None):
4849
self.allen_id = None
4950
self.cluster_chns = None
5051
self.resolved = None
52+
self.alyx_str = None
5153

5254
def get_subjects(self):
5355
"""
@@ -210,7 +212,12 @@ def get_data(self):
210212
'_iblqc_ephysTimeRms.timestamps',
211213
'_iblqc_ephysSpectralDensity.freqs',
212214
'_iblqc_ephysSpectralDensity.power',
213-
'_iblqc_ephysSpectralDensity.amps'
215+
'_iblqc_ephysSpectralDensity.amps',
216+
'_ibl_passiveGabor.table',
217+
'_ibl_passivePeriods.intervalsTable',
218+
'_ibl_passiveRFM.frames',
219+
'_ibl_passiveRFM.times',
220+
'_ibl_passiveStims.table'
214221
]
215222

216223
print(self.subj)
@@ -223,6 +230,11 @@ def get_data(self):
223230

224231
alf_path = Path(self.sess_path, 'alf', self.probe_label)
225232
ephys_path = Path(self.sess_path, 'raw_ephys_data', self.probe_label)
233+
234+
cluster_file_old = alf_path.joinpath('clusters.metrics.csv')
235+
if cluster_file_old.exists():
236+
os.remove(cluster_file_old)
237+
226238
try:
227239
self.chn_coords = np.load(Path(alf_path, 'channels.localCoordinates.npy'))
228240
self.chn_depths = self.chn_coords[:, 1]
@@ -364,14 +376,17 @@ def upload_data(self, xyz_channels, channels=True):
364376

365377
return channel_upload
366378

367-
def update_alignments(self, feature, track, key_info=None):
379+
def update_alignments(self, feature, track, key_info=None, user_eval=None):
368380
if not key_info:
369381
user = self.one._par.ALYX_LOGIN
370382
date = datetime.now().replace(microsecond=0).isoformat()
371-
data = {date + '_' + user: [feature.tolist(), track.tolist()]}
383+
data = {date + '_' + user: [feature.tolist(), track.tolist(), self.alyx_str]}
372384
else:
373385
user = key_info[20:]
374-
data = {key_info: [feature.tolist(), track.tolist()]}
386+
if user_eval:
387+
data = {key_info: [feature.tolist(), track.tolist(), user_eval]}
388+
else:
389+
data = {key_info: [feature.tolist(), track.tolist()]}
375390

376391
old_user = [key for key in self.alignments.keys() if user in key]
377392
# Only delete duplicated if trajectory is not resolved
@@ -392,20 +407,27 @@ def update_json(self, json_data):
392407
def upload_dj(self, align_qc, ephys_qc, ephys_desc):
393408
# Upload qc results to datajoint table
394409
user = self.one._par.ALYX_LOGIN
395-
if ephys_desc == 'None':
396-
ephys_desc = None
410+
if len(ephys_desc) == 0:
411+
ephys_desc_str = 'None'
412+
ephys_dj_str = None
413+
else:
414+
ephys_desc_str = ", ".join(ephys_desc)
415+
ephys_dj_str = ephys_desc_str
416+
397417
self.qc.insert1(dict(probe_insertion_uuid=self.probe_id, user_name=user,
398-
alignment_qc=align_qc, ephys_qc=ephys_qc, ephys_qc_description=ephys_desc),
418+
alignment_qc=align_qc, ephys_qc=ephys_qc,
419+
ephys_qc_description=ephys_dj_str),
399420
allow_direct_insert=True, replace=True)
421+
self.alyx_str = ephys_qc.upper() + ': ' + ephys_desc_str
400422

401423
def update_qc(self, upload_alyx=True, upload_flatiron=True):
402424
# if resolved just update the alignment_number
403-
404425
align_qc = AlignmentQC(self.probe_id, one=self.one, brain_atlas=self.brain_atlas)
405426
align_qc.load_data(prev_alignments=self.alignments, xyz_picks=self.xyz_picks,
406427
depths=self.chn_depths, cluster_chns=self.cluster_chns)
407428
results = align_qc.run(update=True, upload_alyx=upload_alyx,
408429
upload_flatiron=upload_flatiron)
430+
align_qc.update_experimenter_evaluation(prev_alignments=self.alignments)
409431

410432
self.resolved = results['alignment_resolved']
411433

0 commit comments

Comments
 (0)