Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5e7dca6
initial commit for GUI
danielparthier May 27, 2025
4573366
add meta data fields
danielparthier May 28, 2025
54edebe
add object_id (uuid4)
danielparthier Jun 17, 2025
7e48e4e
add label for command current
danielparthier Jun 20, 2025
fd1e7c9
add datetime estimate from header
danielparthier Jun 23, 2025
96aa024
add color picker for pyqt
danielparthier Jul 15, 2025
f18039b
add pyqt plot for trace
danielparthier Jul 15, 2025
c5151f8
Add initial testing framework structure
danielparthier Jul 15, 2025
26002de
refactor trace load and initiation
danielparthier Jul 15, 2025
0ff9066
add regions for window and change window of trace interactively
danielparthier Jul 16, 2025
028551c
remove note
danielparthier Jul 17, 2025
3de9fe2
move plotting to separate file
danielparthier Jul 18, 2025
921cd9b
file for plotting traces (matplotlib/pyqtgraph)
danielparthier Jul 18, 2025
9a3c3e2
add type and input checks and simplify notation
danielparthier Jul 18, 2025
35f0c7b
update function name
danielparthier Jul 18, 2025
983b0a8
update window handling
danielparthier Jul 18, 2025
7daa5e8
fix alpha value for matplotlib
danielparthier Jul 18, 2025
fa84619
add reset_window_summary, refactor for clarity
danielparthier Jul 23, 2025
f0295ee
standardise docstrings
danielparthier Jul 23, 2025
6fcb799
standardise doc strings, add unit and refactor plotting (objects)
danielparthier Jul 23, 2025
5bad6c8
move parameter object, add time array processing function, fix theme …
danielparthier Jul 23, 2025
fb91afb
standardise docstrings, add functions to check for labels
danielparthier Jul 23, 2025
1b141c3
class to plotting parameters
danielparthier Jul 23, 2025
4021ccb
functions to plot window summary
danielparthier Jul 23, 2025
d0d1eef
fix plotting limits and typing
danielparthier Jul 23, 2025
86c4177
Merge pull request #18 from danielparthier:add_pyqtgraph
danielparthier Jul 23, 2025
a3cf5ed
add line width statement to properties
danielparthier Jul 29, 2025
cbebef8
fix module paths
danielparthier Jul 29, 2025
dcfde47
initiate GUI config for future parameters
danielparthier Jul 29, 2025
544c5b0
refactor themes and functions for applying themes
danielparthier Jul 29, 2025
e3352ec
specify module paths
danielparthier Jul 29, 2025
709f5b6
Refactor GUI structure by organizing components into separate modules…
danielparthier Jul 30, 2025
9d84a7b
add window widget
danielparthier Jul 30, 2025
1a417d1
refactoring theme
danielparthier Jul 30, 2025
f2bcafa
only plot "Time" label on bottom channel
danielparthier Jul 30, 2025
fcf81cd
add add_file and reindent
danielparthier Jul 30, 2025
013d08d
add axis for sliding_window_view (bug fix)
danielparthier Jul 30, 2025
e28cc28
add todo label
danielparthier Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ephys/GUI/GUI_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
theme = "dark"
Empty file added ephys/GUI/__init__.py
Empty file.
91 changes: 91 additions & 0 deletions ephys/GUI/gui_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from __future__ import annotations

import sys
from typing import TYPE_CHECKING

from PySide6.QtCore import (
Qt,
QThreadPool,
QRunnable,
Slot,
QObject,
Signal,
)
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
QApplication,
QFrame,
QMainWindow,
QSplitter,
QTabWidget,
QWidget,
)

# from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
# from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar


from .styles import apply_style
from ephys.classes.experiment_objects import ExpData
from ephys.GUI.sidemenu import SideMenu
from ephys.GUI.sessioninfo import SessionInfo
from ephys.GUI.trace_view import TracePlotWindow
from ephys.GUI.sidebar_right import SideBarRight
from ephys.GUI.menubar import FileMenu
from ephys.GUI.mainwindow import MainWindow

# filepath: /home/daniel/Work/RETAIN/Code/MossyFibre/ephys/GUI/gui_app.py


class DataLoader(QRunnable):
def __init__(self, file_path, experimenter_name):
super().__init__()
self.file_path = file_path
self.experimenter_name = experimenter_name
self.data: ExpData | None = None
self.signals = WorkerSignals()

@Slot()
def run(self):
self.data = ExpData(
file_path=self.file_path, experimenter=self.experimenter_name
)

try:
results = self.get_data()
except Exception as e:
self.signals.error_occurred.emit(str(e))
else:
if results is not None:
self.signals.data_ready.emit(results)
else:
self.signals.error_occurred.emit("No data loaded")
finally:
self.signals.finished.emit()

def get_data(self) -> None | ExpData:
if self.data is None:
print("Data could not be loaded")
return None
return self.data


class WorkerSignals(QObject):
"""
Custom signal class to emit data from the worker thread.
"""

finished = Signal()
data_ready = Signal(ExpData)
error_occurred = Signal(str)


app = QApplication(sys.argv)
theme = "dark"
style: str = apply_style(theme=theme)
app.setStyleSheet(style)

window = MainWindow()
window.show()

app.exec()
122 changes: 122 additions & 0 deletions ephys/GUI/labfolder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from PySide6.QtWidgets import (
QMainWindow,
QVBoxLayout,
QWidget,
QLineEdit,
QPushButton,
QHBoxLayout,
QMessageBox,
)
from PySide6.QtCore import Qt
from ephys.labfolder.classes.labfolder_access import LabFolderUserInfo, labfolder_login
from ephys.labfolder.labfolder_config import labfolder_url

if TYPE_CHECKING:
from ephys.GUI.sidemenu import SideMenu


class LabfolderLoginWindow(QMainWindow):
def __init__(self, on_confirm=None) -> None:
self.on_confirm = on_confirm
self.username = None
self.password = None
self.clicked_status = False
super().__init__()
self.setWindowTitle("Labfolder Login")
self.setGeometry(100, 100, 300, 100)
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
self.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
self.setAttribute(Qt.WidgetAttribute.WA_AlwaysShowToolTips)
self.setAttribute(Qt.WidgetAttribute.WA_AlwaysStackOnTop)

# Create a layout for the login window
login_layout = QVBoxLayout()
# Create a QLineEdit for the username
username_input = QLineEdit()
username_input.setPlaceholderText("[email protected]")
login_layout.addWidget(username_input)
# Create a QLineEdit for the password
password_input = QLineEdit()
password_input.setPlaceholderText("Password")
password_input.setEchoMode(QLineEdit.EchoMode.Password)
login_layout.addWidget(password_input)
# Create a button to confirm login
button_layout = QHBoxLayout()
login_button = QPushButton("Login")
login_button.setCheckable(False)
# Button as default button (enter key)
login_button.setDefault(True)
login_button.clicked.connect(self.confirm_and_close)
button_layout.addWidget(login_button)
# Create a button to cancel login
cancel_button = QPushButton("Cancel")
cancel_button.setCheckable(False)
cancel_button.clicked.connect(self.close)
button_layout.addWidget(cancel_button)
login_layout.addLayout(button_layout)
# Create a central widget to hold the layout
central_widget = QWidget()
central_widget.setLayout(login_layout)
self.setCentralWidget(central_widget)
# Set the layout to the central widget
self.setLayout(login_layout)

self.username_input: QLineEdit = username_input
self.password_input: QLineEdit = password_input

def confirm_and_close(self) -> None:
if self.on_confirm:
self.on_confirm(
self.username_input.text(),
self.password_input.text(),
)
self.close()


class LabfolderWindow(QPushButton):
def __init__(self, side_menu: SideMenu):
super().__init__("Labfolder Login")
self.side_menu: SideMenu = side_menu
self.username = ""

self.setMaximumWidth(150)
self.setCheckable(False)
self.clicked.connect(self.login_labfolder)

def login_labfolder(self) -> None:
# Open the LabfolderLoginWindow as a separate window and keep it open
self.labfolder_login_window = LabfolderLoginWindow(
on_confirm=self.get_labfolder_auth
)
self.labfolder_login_window.show()
# make sure that content of labfolder_login_window is transferred to the main window
print("Labfolder login window opened")

def get_labfolder_auth(self, username: str, password: str) -> None:
self.username = username
self.labfolder_auth: LabFolderUserInfo = labfolder_login(
labfolder_url=labfolder_url,
user=username,
password=password,
allow_input=False,
)
if len(self.labfolder_auth.auth_token) == 0:
print("Labfolder authentication failed")
return None

popup_message = QMessageBox(self)
popup_message.setWindowTitle("Labfolder Login Successful")
popup_message.setText("Hello " + self.labfolder_auth.first_name + "!")
popup_message.setWindowOpacity(0.5)
popup_message.exec()
self.current_user = (
self.labfolder_auth.first_name + " " + self.labfolder_auth.last_name
)
self.side_menu.current_user_label.setPlaceholderText(self.current_user)
# add popup message that login was successful
# Close the login window
self.labfolder_login_window.close()
131 changes: 131 additions & 0 deletions ephys/GUI/mainwindow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from typing import cast

from PySide6.QtCore import Qt, QThreadPool
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QFrame, QMainWindow, QSplitter, QTabWidget

from ephys.classes.experiment_objects import ExpData
from ephys.GUI.sessioninfo import SessionInfo
from ephys.GUI.sidemenu import SideMenu, SideMenuContainer
from ephys.GUI.sidebar_right import SideBarRight
from ephys.GUI.menubar import FileMenu
from ephys.GUI.trace_view import TracePlotWindow


class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.clicked_status = False
self.data: ExpData | None = None
self.session_info = SessionInfo()

self.threadpool = QThreadPool()
thread_count = self.threadpool.maxThreadCount()
print(f"Multithreading enabled with {thread_count} threads.")

self.setWindowTitle("EphysTools")
self.setGeometry(100, 100, 1200, 800)

file_menu = FileMenu()
self.setMenuBar(file_menu)

icon = QIcon("logos/Logo_short.svg")
icon_action = QAction(icon, "", self)

# Add a status bar
self.status_bar = self.statusBar()
self.status_bar.addAction(icon_action)
self.status_bar.showMessage("Ready")

# Create a splitter
splitter = QSplitter()
splitter.setOrientation(Qt.Orientation.Horizontal)
splitter.setMaximumHeight(2400)
self.setCentralWidget(splitter)
# Create a frame

sidebar_frame = QFrame()
sidebar_frame.setFrameShape(QFrame.Shape.StyledPanel)
sidebar_frame.setMinimumWidth(150)
sidebar_frame.setMaximumWidth(300)
sidebar_frame.setMinimumHeight(150)
sidebar_frame.setMaximumHeight(1200)

self.side_menu_container = SideMenuContainer()
self.side_menu_container.setParent(sidebar_frame)
self.side_menu_container.set_background_svg(svg_path="logos/Logo_short.svg")
# splitter.addWidget(sidebar_frame)
# Create a layout for the frame
menu_layout_left = SideMenu(self)

sidebar_frame.setLayout(menu_layout_left)

splitter.addWidget(sidebar_frame)
# splitter.addWidget(menu_layout_left)

self.trace_plot = QTabWidget()
self.trace_plot.setTabsClosable(True)
self.trace_plot.setMovable(True)
self.trace_plot.setMinimumWidth(800)
self.trace_plot.setMinimumHeight(600)
self.trace_plot.setMaximumWidth(2400)
self.trace_plot.setMaximumHeight(1200)
self.trace_plot.setTabsClosable(True)
self.trace_plot.setMovable(True)
self.trace_plot.tabCloseRequested.connect(self.close_tab)
splitter.addWidget(self.trace_plot)

# Add a sidebar on the right side
sidebar_right = SideBarRight(self)
sidebar_right.setMinimumWidth(180)
sidebar_right.setMaximumWidth(300)
sidebar_right.setMinimumHeight(150)
sidebar_right.setMaximumHeight(1200)
splitter.addWidget(sidebar_right)

menu_layout_left.addWidget(self.side_menu_container)
# Add the session info widget

# decouple tab_widget from the splitter

self.show()

def closeEvent(self, event):
print("Window closed")
event.accept()

def close_tab(self, index: int) -> None:
"""Close the tab at the given index."""
tab_selected: TracePlotWindow = cast(
TracePlotWindow, self.trace_plot.widget(index)
)
print(index, tab_selected)
if index >= 0:
self.trace_plot.removeTab(index)
print(f"Closed tab at index {index}")
if hasattr(tab_selected, "cleanup") and callable(
getattr(tab_selected, "cleanup", None)
):
tab_selected.cleanup()
if tab_selected is not None:
# Ensure the tab is properly deleted
if hasattr(tab_selected, "deleteLater"):
tab_selected.deleteLater()
else:
print("Tab selected does not have deleteLater method")

# def choose_file(self):
# # Store the dialog as an instance attribute to prevent it from being deleted
# self.get_file = FileSelector()
# # self.get_file.exec()
# file_path = self.get_file.getOpenFileNames(
# self, "Select WCP files", "data/WCP/WholeCell", "WCP files (*.wcp)"
# )[0]
# # if not file_path:
# # print("No files selected")
# # return None
# print(f"Selected files: {file_path}")
# self.the_button_was_clicked(file_path)
# if isinstance(file_path, str):
# file_path = [file_path]
# self.file_list = file_path
Loading
Loading