Skip to content

Commit 04ef5bc

Browse files
committed
sort dataframe, store UI settings
1 parent 4a3627f commit 04ef5bc

File tree

2 files changed

+78
-26
lines changed

2 files changed

+78
-26
lines changed

ibllib/qc/task_qc_viewer/ViewEphysQC.py

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@
33
import logging
44

55
from PyQt5 import QtWidgets
6-
from PyQt5.QtCore import Qt, QModelIndex, QPoint, pyqtSignal, pyqtSlot, QCoreApplication, QSettings
6+
from PyQt5.QtCore import (
7+
Qt,
8+
QModelIndex,
9+
pyqtSignal,
10+
pyqtSlot,
11+
QCoreApplication,
12+
QSettings,
13+
QSize,
14+
QPoint,
15+
)
716
from PyQt5.QtGui import QPalette, QShowEvent
817
from PyQt5.QtWidgets import QMenu, QAction
918
from iblqt.core import ColoredDataFrameTableModel
@@ -24,14 +33,10 @@ def __init__(self, parent=None, width=5, height=4, dpi=100, wheel=None):
2433
FigureCanvasQTAgg.__init__(self, fig)
2534
self.setParent(parent)
2635

27-
FigureCanvasQTAgg.setSizePolicy(
28-
self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
29-
)
36+
FigureCanvasQTAgg.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
3037
FigureCanvasQTAgg.updateGeometry(self)
3138
if wheel:
32-
self.ax, self.ax2 = fig.subplots(
33-
2, 1, gridspec_kw={"height_ratios": [2, 1]}, sharex=True
34-
)
39+
self.ax, self.ax2 = fig.subplots(2, 1, gridspec_kw={'height_ratios': [2, 1]}, sharex=True)
3540
else:
3641
self.ax = fig.add_subplot(111)
3742
self.draw()
@@ -53,12 +58,10 @@ class GraphWindow(QtWidgets.QWidget):
5358
def __init__(self, parent=None, wheel=None):
5459
QtWidgets.QWidget.__init__(self, parent=parent)
5560

56-
# Store layout changes to QSettings
57-
self.settings = QSettings()
58-
5961
self.columnPinned = pyqtSignal(int, bool)
6062

61-
self.pushButtonLoad = QtWidgets.QPushButton("Select File", self)
63+
# load button
64+
self.pushButtonLoad = QtWidgets.QPushButton('Select File', self)
6265
self.pushButtonLoad.clicked.connect(self.loadFile)
6366

6467
# define table model & view
@@ -70,6 +73,7 @@ def __init__(self, parent=None, wheel=None):
7073
self.tableView.horizontalHeader().setSectionsMovable(True)
7174
self.tableView.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
7275
self.tableView.horizontalHeader().customContextMenuRequested.connect(self.contextMenu)
76+
self.tableView.verticalHeader().hide()
7377
self.tableView.doubleClicked.connect(self.tv_double_clicked)
7478

7579
# define colors for highlighted cells
@@ -98,6 +102,7 @@ def __init__(self, parent=None, wheel=None):
98102

99103
# slider for alpha values
100104
self.sliderAlpha = QtWidgets.QSlider(Qt.Horizontal, self)
105+
self.sliderAlpha.setMaximumWidth(100)
101106
self.sliderAlpha.setMinimum(0)
102107
self.sliderAlpha.setMaximum(255)
103108
self.sliderAlpha.setValue(self.tableModel.alpha)
@@ -111,22 +116,36 @@ def __init__(self, parent=None, wheel=None):
111116
hLayout.addWidget(self.comboboxColormap)
112117
hLayout.addWidget(QtWidgets.QLabel('Alpha', self))
113118
hLayout.addWidget(self.sliderAlpha)
114-
hLayout.addStretch(1)
119+
hLayout.addSpacing(50)
115120
hLayout.addWidget(self.pushButtonLoad)
116121

117122
# Vertical layout
118123
vLayout = QtWidgets.QVBoxLayout(self)
119124
vLayout.addLayout(hLayout)
120125
vLayout.addWidget(self.tableView)
121126

122-
self.setMinimumSize(500, 400)
127+
# Recover layout from QSettings
128+
self.settings = QSettings()
129+
self.settings.beginGroup('MainWindow')
130+
self.resize(self.settings.value('size', QSize(800, 600), QSize))
131+
self.comboboxColormap.setCurrentText(self.settings.value('colormap', 'plasma', str))
132+
self.sliderAlpha.setValue(self.settings.value('alpha', 255, int))
133+
self.settings.endGroup()
123134

124135
self.wplot = PlotWindow(wheel=wheel)
125136
self.wplot.show()
126137
self.tableModel.dataChanged.connect(self.wplot.canvas.draw)
127138

128139
self.wheel = wheel
129140

141+
def closeEvent(self, _) -> bool:
142+
self.settings.beginGroup('MainWindow')
143+
self.settings.setValue('size', self.size())
144+
self.settings.setValue('colormap', self.tableModel.colormap)
145+
self.settings.setValue('alpha', self.tableModel.alpha)
146+
self.settings.endGroup()
147+
self.wplot.close()
148+
130149
def showEvent(self, a0: QShowEvent) -> None:
131150
super().showEvent(a0)
132151
self.activateWindow()
@@ -151,36 +170,64 @@ def pinColumn(self, pin: bool, idx: int | None = None):
151170
self.changeFilter(self.lineEditFilter.text())
152171

153172
def changeFilter(self, string: str):
154-
headers = [self.tableModel.headerData(x, Qt.Horizontal, Qt.DisplayRole).value().lower()
155-
for x in range(self.tableModel.columnCount())]
173+
headers = [
174+
self.tableModel.headerData(x, Qt.Horizontal, Qt.DisplayRole).value().lower()
175+
for x in range(self.tableModel.columnCount())
176+
]
156177
tokens = [y.lower() for y in (x.strip() for x in string.split(',')) if len(y)]
157178
showAll = len(tokens) == 0
158179
for idx, column in enumerate(headers):
159180
show = showAll or any((t in column for t in tokens)) or idx in self._pinnedColumns
160181
self.tableView.setColumnHidden(idx, not show)
161182

162183
def loadFile(self):
163-
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
164-
self, "Open File", "", "CSV Files (*.csv)"
165-
)
184+
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open File', '', 'CSV Files (*.csv)')
166185
if len(fileName) == 0:
167186
return
168187
df = pd.read_csv(fileName)
169188
self.updateDataframe(df)
170189

171-
def updateDataframe(self, dataFrame: pd.DataFrame):
172-
self.tableModel.setDataFrame(dataFrame)
190+
def updateDataframe(self, df: pd.DataFrame):
191+
# clear pinned columns
192+
self._pinnedColumns = []
193+
194+
# try to identify and sort columns containing timestamps
195+
col_names = df.columns
196+
df_interp = df.replace([-np.inf, np.inf], np.nan)
197+
df_interp = df_interp.interpolate(limit_direction='both')
198+
cols_mono = col_names[[df_interp[c].is_monotonic_increasing for c in col_names]]
199+
cols_mono = [c for c in cols_mono if df[c].nunique() > 1]
200+
cols_mono = df_interp[cols_mono].mean().sort_values().keys()
201+
for idx, col_name in enumerate(cols_mono):
202+
df.insert(idx, col_name, df.pop(col_name))
203+
204+
# columns containing boolean values are sorted to the end
205+
cols_bool = list(df.select_dtypes('bool').columns)
206+
cols_pass = [cols_bool.pop(i) for i, c in enumerate(cols_bool) if 'pass' in c]
207+
cols_bool += cols_pass
208+
for col_name in cols_bool:
209+
df = df.join(df.pop(col_name))
210+
211+
# trial_no should always be the first column
212+
if 'trial_no' in col_names:
213+
df.insert(0, 'trial_no', df.pop('trial_no'))
214+
215+
# define columns that should be pinned by default
216+
for col in ['trial_no']:
217+
self._pinnedColumns.append(df.columns.get_loc(col))
218+
219+
self.tableModel.setDataFrame(df)
173220

174221
def tv_double_clicked(self, index: QModelIndex):
175222
data = self.tableModel.dataFrame.iloc[index.row()]
176-
t0 = data["intervals_0"]
177-
t1 = data["intervals_1"]
223+
t0 = data['intervals_0']
224+
t1 = data['intervals_1']
178225
dt = t1 - t0
179226
if self.wheel:
180-
idx = np.searchsorted(self.wheel["re_ts"], np.array([t0 - dt / 10, t1 + dt / 10]))
181-
period = self.wheel["re_pos"][idx[0] : idx[1]]
227+
idx = np.searchsorted(self.wheel['re_ts'], np.array([t0 - dt / 10, t1 + dt / 10]))
228+
period = self.wheel['re_pos'][idx[0] : idx[1]]
182229
if period.size == 0:
183-
_logger.warning("No wheel data during trial #%i", index.row())
230+
_logger.warning('No wheel data during trial #%i', index.row())
184231
else:
185232
min_val, max_val = np.min(period), np.max(period)
186233
self.wplot.canvas.ax2.set_ylim(min_val - 1, max_val + 1)
@@ -191,10 +238,11 @@ def tv_double_clicked(self, index: QModelIndex):
191238

192239

193240
def viewqc(qc=None, title=None, wheel=None):
241+
app = qt.create_app()
242+
app.setStyle('Fusion')
194243
QCoreApplication.setOrganizationName('International Brain Laboratory')
195244
QCoreApplication.setOrganizationDomain('internationalbrainlab.org')
196245
QCoreApplication.setApplicationName('QC Viewer')
197-
qt.create_app()
198246
qcw = GraphWindow(wheel=wheel)
199247
qcw.setWindowTitle(title)
200248
if qc is not None:

ruff.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
line-length = 130
2+
3+
[format]
4+
quote-style = "single"

0 commit comments

Comments
 (0)