Skip to content

Commit f8b5b51

Browse files
authored
Merge pull request #163 from MouseLand/dev
Add saccade loading & plotting
2 parents 2c12b4b + 5df1f25 commit f8b5b51

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

.github/workflows/test_and_deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ jobs:
9090
run: |
9191
git tag
9292
python setup.py sdist bdist_wheel
93-
twine upload dist/*
93+
twine upload dist/*

facemap/gui/gui.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
QStatusBar,
3131
QToolButton,
3232
QWidget,
33+
QGraphicsItemGroup
3334
)
3435
from scipy.stats import skew, zscore
35-
3636
from facemap import process, roi, utils
3737
from facemap.gui import (
3838
guiparts,
@@ -265,6 +265,7 @@ def __init__(
265265
self.cframe = 0
266266
self.traces1 = None
267267
self.traces2 = None
268+
self.saccade_data = None
268269

269270
## Pose plot
270271
self.pose_scatterplot = pg.ScatterPlotItem(hover=True)
@@ -939,6 +940,7 @@ def reset(self):
939940
self.keypoints_vtick = None
940941
self.svd_plot_vtick = None
941942
self.neural_win = None
943+
self.saccade_data = None
942944

943945
def pupil_sigma_change(self):
944946
self.pupil_sigma = float(self.sigma_box.text())
@@ -1286,7 +1288,9 @@ def process_ROIs(self):
12861288
savepath = self.save_path
12871289
else:
12881290
savepath = None
1289-
if self.motSVD_checkbox.isChecked() or self.movSVD_checkbox.isChecked():
1291+
print("Checking if ROIs are set")
1292+
if self.motSVD_checkbox.isChecked() or self.movSVD_checkbox.isChecked() or len(self.ROIs) > 0:
1293+
print("Processing ROIs")
12901294
savename = process.run(
12911295
self.filenames, GUIobject=QtWidgets, parent=self, savepath=savepath
12921296
)
@@ -2009,9 +2013,59 @@ def plot_trace(self, wplot, proctype, wroi, color, keypoints_group_selected=None
20092013
pen=pg.mkPen(color=(255, 255, 255), width=2, movable=True),
20102014
)
20112015
selected_plot.addItem(self.keypoints_vtick)
2016+
if self.saccade_data is not None:
2017+
self.plot_saccade_data()
20122018
selected_plot.setLimits(xMin=0, xMax=self.nframes)
20132019
return tr
20142020

2021+
def plot_saccade_data(self):
2022+
"""
2023+
Plot saccade data on the SVD traces plot as a single toggleable item.
2024+
"""
2025+
if self.saccade_data is not None:
2026+
# Extract saccade data
2027+
saccade = self.saccade_data['Saccade'][0, 0].squeeze()
2028+
2029+
# Check if saccade data matches the number of frames
2030+
if saccade.shape[0] != self.nframes:
2031+
print("Saccade data shape does not match the number of frames.")
2032+
return
2033+
2034+
# Find start and end indices for saccades
2035+
sac_start_idx = np.where(np.diff(saccade) == 1)[0] + 1
2036+
sac_end_idx = np.where(np.diff(saccade) == -1)[0] + 1
2037+
2038+
# Remove any existing saccade overlays to avoid duplication
2039+
if hasattr(self, 'saccade_vspan_items') and self.saccade_vspan_items:
2040+
for item in self.saccade_vspan_items:
2041+
self.svd_traces_plot.removeItem(item)
2042+
2043+
# Create a list to store the saccade region items
2044+
self.saccade_vspan_items = []
2045+
2046+
# Add vertical spans for each saccade
2047+
for start, end in zip(sac_start_idx, sac_end_idx):
2048+
if start < end and end < self.nframes:
2049+
# Create a LinearRegionItem for the saccade
2050+
vspan = pg.LinearRegionItem(
2051+
values=(start, end), # Start and end positions
2052+
brush=pg.mkBrush(255, 255, 255, 50), # Semi-transparent white
2053+
movable=False, # Non-movable
2054+
)
2055+
2056+
# Add the region to the plot
2057+
self.svd_traces_plot.addItem(vspan)
2058+
2059+
# Keep track of added items for later removal
2060+
self.saccade_vspan_items.append(vspan)
2061+
else:
2062+
print("No saccade data available.")
2063+
2064+
def toggle_saccade_vspans(self, show):
2065+
# Toggles visibility of the saccade vspans group
2066+
if hasattr(self, 'saccade_vspan_group'): # Check if the group exists
2067+
self.saccade_vspan_group.setVisible(show)
2068+
20152069
def on_click_svd_plot(self, event):
20162070
"""
20172071
Update vtick position of svd plot when user clicks

facemap/gui/io.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,24 @@ def load_movies(parent, filelist=None):
354354
parent.load_keypoints_from_videodir()
355355
return good
356356

357+
def load_saccade(parent):
358+
path = QFileDialog.getOpenFileName(parent, "Select a file", filter="MAT (*.mat)")
359+
# Check if path exists
360+
if path[0]:
361+
try:
362+
import scipy.io
363+
364+
saccade_data = scipy.io.loadmat(path[0])
365+
parent.saccade_data = saccade_data['eye_movement_data']
366+
parent.update_status_bar("Saccade data loaded")
367+
parent.plot_saccade_data()
368+
except Exception as e:
369+
msg = QMessageBox(parent)
370+
msg.setIcon(QMessageBox.Icon.Warning)
371+
msg.setText("Error loading saccade data: " + str(e))
372+
msg.setStandardButtons(QMessageBox.StandardButton.Ok)
373+
msg.exec_()
374+
357375

358376
def load_npy_file(parent, allow_mat=False):
359377
# Open a file dialog to select a folder

facemap/gui/menus.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ def mainmenu(parent):
2323
load_proc.triggered.connect(lambda: io.open_proc(parent))
2424
parent.addAction(load_proc)
2525

26+
# Load saccade data
27+
load_saccade = QAction("Load saccade data", parent)
28+
load_saccade.triggered.connect(lambda: io.load_saccade(parent))
29+
parent.addAction(load_saccade)
30+
2631
# Set output folder
2732
set_output_folder = QAction("Set output folder", parent)
2833
set_output_folder.setShortcut("Ctrl+S")
@@ -95,6 +100,7 @@ def mainmenu(parent):
95100
file_menu.addAction(open_file)
96101
file_menu.addAction(open_folder)
97102
file_menu.addAction(load_proc)
103+
file_menu.addAction(load_saccade)
98104
file_menu.addAction(set_output_folder)
99105

100106
pose_menu = main_menu.addMenu("Pose")

0 commit comments

Comments
 (0)