Skip to content

Commit

Permalink
BciPy report action, offline analysis updates, Intertask GUI. (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
tab-cmd authored Oct 20, 2024
1 parent a531946 commit 0dd8b50
Show file tree
Hide file tree
Showing 35 changed files with 714 additions and 259 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ exclude_lines =
raise NotImplementedError
@abstract
if __name__ == .__main__.:
log = logging.getLogger(__name__)
logging.getLogger(__name__)
1 change: 1 addition & 0 deletions bcipy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
TASK_SEPERATOR = '->'

DEFAULT_PARAMETER_FILENAME = 'parameters.json'
DEFAULT_DEVICES_PATH = f"{BCIPY_ROOT}/parameters"
DEFAULT_PARAMETERS_PATH = f'{BCIPY_ROOT}/parameters/{DEFAULT_PARAMETER_FILENAME}'
DEFAULT_DEVICE_SPEC_FILENAME = 'devices.json'
DEVICE_SPEC_PATH = f'{BCIPY_ROOT}/parameters/{DEFAULT_DEVICE_SPEC_FILENAME}'
Expand Down
17 changes: 13 additions & 4 deletions bcipy/gui/BCInterface.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import subprocess
import sys
import logging
from typing import List

from bcipy.config import (BCIPY_ROOT, DEFAULT_PARAMETERS_PATH,
STATIC_IMAGES_PATH)
STATIC_IMAGES_PATH, PROTOCOL_LOG_FILENAME)
from bcipy.gui.main import (AlertMessageResponse, AlertMessageType,
AlertResponse, BCIGui, app,
contains_special_characters, contains_whitespaces,
Expand All @@ -12,6 +13,8 @@
load_json_parameters, load_users)
from bcipy.task import TaskRegistry

logger = logging.getLogger(PROTOCOL_LOG_FILENAME)


class BCInterface(BCIGui):
"""BCI Interface.
Expand All @@ -29,7 +32,7 @@ class BCInterface(BCIGui):
max_length = 25
min_length = 1
timeout = 3
font = 'Consolas'
font = 'Courier New'

def __init__(self, *args, **kwargs):
super(BCInterface, self).__init__(*args, **kwargs)
Expand Down Expand Up @@ -420,7 +423,13 @@ def start_experiment(self) -> None:
)
if self.alert:
cmd += ' -a'
subprocess.Popen(cmd, shell=True)
output = subprocess.run(cmd, shell=True)
if output.returncode != 0:
self.throw_alert_message(
title='BciPy Alert',
message=f'Error: {output.stderr.decode()}',
message_type=AlertMessageType.CRIT,
message_response=AlertMessageResponse.OTE)

if self.autoclose:
self.close()
Expand All @@ -431,7 +440,7 @@ def offline_analysis(self) -> None:
Run offline analysis as a script in a new process.
"""
if not self.action_disabled():
cmd = f'python {BCIPY_ROOT}/signal/model/offline_analysis.py --alert --p "{self.parameter_location}"'
cmd = f'bcipy-train --alert --p "{self.parameter_location}" -v -s'
subprocess.Popen(cmd, shell=True)

def action_disabled(self) -> bool:
Expand Down
5 changes: 3 additions & 2 deletions bcipy/gui/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ def confirm(message: str) -> bool:
-------
users selection : True for selecting Ok, False for Cancel.
"""
app = QApplication(sys.argv)
app = QApplication(sys.argv).instance()
if not app:
app = QApplication(sys.argv)
dialog = alert_message(message,
message_type=AlertMessageType.INFO,
message_response=AlertMessageResponse.OCE)
button = dialog.exec()

result = bool(button == AlertResponse.OK.value)
app.quit()
return result
63 changes: 53 additions & 10 deletions bcipy/gui/bcipy_stylesheet.css
Original file line number Diff line number Diff line change
@@ -1,44 +1,87 @@
/* This stylesheet uses the QSS syntax, but is named as a CSS file to take advantage of IDE CSS tooling */
* {
background-color: white;

QWidget[class="experiment-registry"] {
background-color: black;
}

QWidget[class="inter-task"] {
background-color: black;
}
QWidget {

QLabel {
background-color: black;
color: white;
font-size: 14px;
}

QWidget > * {
background-color: none;
QLabel[class="task-label"] {
background-color: transparent;
color: black;
}

QPushButton {
background-color: green;
background-color: rgb(16, 173, 39);
color: white;
padding: 10px;
border-radius: 10px;
}

QPushButton[class="remove-button"] {
background-color: rgb(243, 58, 58);
}

QPushButton[class="remove-button"]:hover {
background-color: rgb(255, 0, 0);
}

QPushButton[class="small-button"] {
background-color: darkslategray;
color: white;
padding: 5px;
border-radius: 5px;
}

QPushButton:hover {
QPushButton[class="small-button"]:hover {
background-color: darkgreen;
}


QPushButton:pressed {
background-color: darkslategrey;
}

QComboBox, QLineEdit {
QComboBox {
background-color: white;
color: black;
padding: 4px;
border-radius: 1px;
}

QComboBox:hover {
background-color: #e6f5ea;
color: black;
}

QComboBox:on {
background-color: #e6f5ea;
color: black;
}

QListView {
background-color: white;
color: black;
padding: 5px;
}

QLineEdit {
background-color: white;
color: black;
padding: 5px;
border: none;
border-radius: 1px;
}

QScrollArea {
background-color: white;
}
color: black;
border-radius: 1px;
}
19 changes: 17 additions & 2 deletions bcipy/gui/bciui.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable
from typing import Callable, Type
from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import (
QWidget,
Expand All @@ -9,9 +9,11 @@
QLayout,
QSizePolicy,
QMessageBox,
QApplication,
)
from typing import Optional, List
from bcipy.config import BCIPY_ROOT
import sys


class BCIUI(QWidget):
Expand Down Expand Up @@ -68,7 +70,6 @@ def centered(widget: QWidget) -> QHBoxLayout:

@staticmethod
def make_list_scroll_area(widget: QWidget) -> QScrollArea:
widget.setStyleSheet("background-color: transparent;")
scroll_area = QScrollArea()
scroll_area.setWidget(widget)
scroll_area.setWidgetResizable(True)
Expand Down Expand Up @@ -107,6 +108,10 @@ def toggle_on():
on_button.clicked.connect(toggle_off)
off_button.clicked.connect(toggle_on)

def hide(self) -> None:
"""Close the UI window"""
self.hide()


class SmallButton(QPushButton):
"""A small button with a fixed size"""
Expand Down Expand Up @@ -221,3 +226,13 @@ def list_property(self, prop: str):
A list of values for the given property.
"""
return [widget.data[prop] for widget in self.widgets]


def run_bciui(ui: Type[BCIUI], *args, **kwargs):
# add app to kwargs
app = QApplication(sys.argv).instance()
if not app:
app = QApplication(sys.argv)
ui_instance = ui(*args, **kwargs)
ui_instance.display()
return app.exec()
37 changes: 20 additions & 17 deletions bcipy/gui/experiments/ExperimentRegistry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, Type
from typing import List, Optional
from PyQt6.QtWidgets import (
QComboBox,
QVBoxLayout,
Expand All @@ -7,9 +7,8 @@
QLineEdit,
QPushButton,
QScrollArea,
QApplication,
)
from bcipy.gui.bciui import BCIUI, DynamicItem, DynamicList, SmallButton
from bcipy.gui.bciui import BCIUI, DynamicItem, DynamicList, SmallButton, run_bciui
from bcipy.helpers.load import load_fields, load_experiments
from bcipy.helpers.save import save_experiment_data
from bcipy.config import (
Expand All @@ -33,9 +32,14 @@ class ExperimentRegistry(BCIUI):
def __init__(self):
super().__init__("Experiment Registry", 600, 700)
self.task_registry = TaskRegistry()
self.setProperty("class", "experiment-registry")

def format_experiment_combobox(
self, label_text: str, combobox: QComboBox, buttons: Optional[List[QPushButton]]
self,
label_text: str,
combobox: QComboBox,
buttons: Optional[List[QPushButton]],
class_name: str = 'default',
) -> QVBoxLayout:
"""
Create a formatted widget for a the experiment comboboxes with optional buttons.
Expand All @@ -51,11 +55,11 @@ def format_experiment_combobox(
A QVBoxLayout with the label, combobox, and buttons.
"""
label = QLabel(label_text)
label.setStyleSheet("font-size: 18px")
area = QVBoxLayout()
input_area = QHBoxLayout()
input_area.setContentsMargins(15, 0, 0, 15)
area.addWidget(label)
combobox.setProperty("class", class_name)
input_area.addWidget(combobox, 1)
if buttons:
for button in buttons:
Expand All @@ -77,7 +81,7 @@ def make_task_entry(self, name: str) -> DynamicItem:
"""
layout = QHBoxLayout()
label = QLabel(name)
label.setStyleSheet("color: black;")
label.setProperty("class", "task-label")
layout.addWidget(label)
widget = DynamicItem()

Expand All @@ -102,7 +106,7 @@ def make_task_entry(self, name: str) -> DynamicItem:
layout.addWidget(move_down_button)

remove_button = SmallButton("Remove")
remove_button.setStyleSheet("background-color: red;")
remove_button.setProperty("class", "remove-button")
remove_button.clicked.connect(
lambda: layout.deleteLater()
) # This may not be needed
Expand All @@ -127,7 +131,7 @@ def make_field_entry(self, name: str) -> DynamicItem:
"""
layout = QHBoxLayout()
label = QLabel(name)
label.setStyleSheet("color: black;")
label.setProperty("class", "task-label")
layout.addWidget(label)
widget = DynamicItem()

Expand Down Expand Up @@ -277,7 +281,10 @@ def add_task():
add_task_button = QPushButton("Add")
add_task_button.clicked.connect(add_task)
experiment_protocol_box = self.format_experiment_combobox(
"Protocol", self.experiment_protocol_input, [add_task_button]
"Protocol",
self.experiment_protocol_input,
[add_task_button],
"protocol",
)
form_area.addLayout(experiment_protocol_box)

Expand All @@ -288,7 +295,10 @@ def add_task():
new_field_button.clicked.connect(self.create_experiment_field)
form_area.addLayout(
self.format_experiment_combobox(
"Fields", self.field_input, [add_field_button, new_field_button]
"Fields",
self.field_input,
[add_field_button, new_field_button],
"fields",
)
)

Expand Down Expand Up @@ -317,12 +327,5 @@ def add_task():
self.contents.addWidget(create_experiment_button)


def run_bciui(ui: Type[BCIUI], *args, **kwargs):
app = QApplication([])
ui_instance = ui(*args, **kwargs)
ui_instance.display()
app.exec()


if __name__ == "__main__":
run_bciui(ExperimentRegistry)
Loading

0 comments on commit 0dd8b50

Please sign in to comment.