Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# SigVisualizer

SigVisualizer is a PyQt5 GUI application that visualizes electroencephalogram signals streamed from [__lab streaming layer__ (LSL)](https://github.com/sccn/labstreaminglayer "LabStreamingLayer (LSL)") in real time.
SigVisualizer is a Qt GUI application that visualizes electroencephalogram signals streamed from [__lab streaming layer__ (LSL)](https://github.com/sccn/labstreaminglayer "LabStreamingLayer (LSL)") in real time.

## Requirements
* Python 3.x
* PyQt5
* Python 3.9+
* One of: PyQt5, PyQt6, PySide2, PySide6

![image](SigVisualizer_demo.gif)
![image](docs/SigVisualizer_demo.gif)
1 change: 0 additions & 1 deletion createUI.bat

This file was deleted.

File renamed without changes
36 changes: 36 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "sigvisualizer"
version = "0.1.0"
description = "Real-time Qt GUI for visualizing EEG signals streamed via Lab Streaming Layer (LSL)"
readme = "README.md"
license = { text = "GPL-3.0-or-later" }
requires-python = ">=3.9"
dependencies = [
"qtpy>=2.4",
"pylsl",
]

[project.optional-dependencies]
pyqt5 = ["PyQt5"]
pyqt6 = ["PyQt6"]
pyside2 = ["PySide2"]
pyside6 = ["PySide6"]

[project.urls]
Homepage = "https://github.com/labstreaminglayer/App-SigVisualizer"

[project.scripts]
sigvisualizer = "sigvisualizer.app:main"

[tool.setuptools]
package-dir = { "" = "src" }

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools.package-data]
sigvisualizer = ["resources/*.ico", "resources/*.png", "resources/*.ui", "resources/icons/*.svg"]
File renamed without changes.
82 changes: 0 additions & 82 deletions sigvisualizer.py

This file was deleted.

6 changes: 6 additions & 0 deletions src/sigvisualizer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from importlib.metadata import version, PackageNotFoundError

try:
__version__ = version("sigvisualizer")
except PackageNotFoundError:
__version__ = "0.0.0"
118 changes: 118 additions & 0 deletions src/sigvisualizer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import atexit
import sys
from contextlib import ExitStack
from importlib import resources

from qtpy import uic
from qtpy.QtCore import QSize, Qt, Signal
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QApplication, QMainWindow, QStatusBar, QTreeWidgetItem

from .paintwidget import PaintWidget

_resource_stack = ExitStack()
atexit.register(_resource_stack.close)


def _resource_path(*parts):
ref = resources.files("sigvisualizer").joinpath("resources", *parts)
return str(_resource_stack.enter_context(resources.as_file(ref)))


class SigVisualizer(QMainWindow):
stream_expanded = Signal(str)

def __init__(self):
super().__init__()
uic.loadUi(_resource_path("ui_sigvisualizer.ui"), self)
self._install_paint_widget()

self.setWindowTitle('Real Time Signal Visualizer')
self.treeWidget.setHeaderLabel('Streams')
self.setWindowIcon(QIcon(_resource_path("sigvisualizer.ico")))

self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)

self.toggleButton.setIcon(QIcon(_resource_path("icons", "chevron_left.svg")))
self.toggleButton.setIconSize(QSize(30, 30))

self.toggleButton.clicked.connect(self.toggle_panel)
self.updateButton.clicked.connect(self.widget.dataTr.update_streams)
self.widget.dataTr.updateStreamNames.connect(self.update_metadata_widget)
self.panelHidden = False

self.treeWidget.itemExpanded.connect(self.tree_item_expanded)
self.stream_expanded.connect(self.widget.dataTr.handle_stream_expanded)

def closeEvent(self, event):
self.widget.dataTr.request_stop()
self.widget.dataTr.wait(2000)
super().closeEvent(event)

def _install_paint_widget(self):
"""Replace the plain QWidget placeholder loaded from the .ui file with a real
PaintWidget. Kept as a placeholder in the .ui file (rather than a Designer
"promoted widget") since promoted-widget resolution isn't portable across
PyQt5/PyQt6/PySide2/PySide6."""
layout = self.centralwidget.layout()
index = layout.indexOf(self.widget)
row, col, rowspan, colspan = layout.getItemPosition(index)
layout.removeWidget(self.widget)
self.widget.deleteLater()
self.widget = PaintWidget(self.centralwidget)
self.widget.setObjectName("widget")
layout.addWidget(self.widget, row, col, rowspan, colspan)

def tree_item_expanded(self, widget_item):
name = widget_item.text(0)
for it_ix in range(self.treeWidget.topLevelItemCount()):
item = self.treeWidget.topLevelItem(it_ix)
if item.text(0) != name:
item.setExpanded(False)
self.stream_expanded.emit(name)

def update_metadata_widget(self, metadata, default_idx):
for s_ix, s_meta in enumerate(metadata):
item = QTreeWidgetItem(self.treeWidget)
item.setText(0, s_meta["name"])

for m in range(s_meta["ch_count"]):
channel_item = QTreeWidgetItem(item)
channel_item.setText(0, 'Channel {}'.format(m+1))
channel_item.setCheckState(0, Qt.CheckState.Checked)

item.setExpanded(True if s_ix == default_idx else False)
self.treeWidget.addTopLevelItem(item)

self.treeWidget.setAnimated(True)
if default_idx >= 0:
self.statusBar.showMessage(
"Sampling rate: {}Hz".format(metadata[default_idx]["srate"]))
else:
self.statusBar.showMessage("No signal streams found")

def toggle_panel(self):
if self.panelHidden:
self.panelHidden = False
self.treeWidget.show()
self.updateButton.show()
self.toggleButton.setIcon(QIcon(_resource_path("icons", "chevron_left.svg")))
self.toggleButton.setIconSize(QSize(30, 30))
else:
self.panelHidden = True
self.treeWidget.hide()
self.updateButton.hide()
self.toggleButton.setIcon(QIcon(_resource_path("icons", "chevron_right.svg")))
self.toggleButton.setIconSize(QSize(30, 30))


def main():
app = QApplication(sys.argv)
window = SigVisualizer()
window.show()
sys.exit(app.exec())


if __name__ == '__main__':
main()
Loading