Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Nodes and Redesign #80

Draft
wants to merge 47 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1938550
add nodes-demo
marsipu Oct 9, 2023
864c089
some fixes
marsipu Dec 19, 2023
e98c3fe
unnecessary double in tfr
marsipu Jan 7, 2024
288b210
add_meeg as init-function
marsipu Feb 7, 2024
7e76ead
start implementing nodes basics
marsipu Feb 29, 2024
51d8ebf
first implementation into mainwindow
marsipu Mar 3, 2024
028ef2e
some node groundwork
marsipu Apr 7, 2024
26d69d7
realizing need of own nodes implementation
marsipu Apr 8, 2024
c4b5041
implement base structures (node-graph missing)
marsipu Apr 10, 2024
6241157
node graph implemented
marsipu Apr 12, 2024
b09871a
first fix of nodes
marsipu Apr 12, 2024
40a5099
fix very annoying bug with pos
marsipu Apr 12, 2024
5e9056a
fix basic bugs
marsipu Apr 13, 2024
edb46a7
fix widget width
marsipu Apr 13, 2024
fa7c8a9
add first test
marsipu Apr 13, 2024
fcf6c15
basic test
marsipu Apr 14, 2024
584adc2
fix autolayout and remove ctrl shift selection behaviour
marsipu Apr 14, 2024
a7aff8b
advanced node demo
marsipu Apr 14, 2024
9a9f12c
add debug-mode to nodeviewer from conftest
marsipu May 26, 2024
281a3d1
fix node test
marsipu Jun 22, 2024
89e3136
some changes to controller
marsipu Jun 22, 2024
5e34498
start viewer serialization
marsipu Jun 22, 2024
cfe2cfd
fix annotation
marsipu Aug 10, 2024
1e08ba4
Merge remote-tracking branch 'origin/nodes' into nodes
marsipu Aug 10, 2024
b997020
Continue to/from_dict (in Progress)
marsipu Nov 30, 2024
d3fb3fe
Continue to/from_dict (in Progress 2)
marsipu Dec 15, 2024
f610800
Merge branch 'main' into nodes
marsipu Dec 15, 2024
8fb7047
fix connected nodes
marsipu Dec 15, 2024
b46840d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 15, 2024
7dfe8a8
fix QPen.setStyle deprecated in PyQt6
marsipu Jan 2, 2025
968cd95
Merge branch 'main' into nodes
marsipu Jan 19, 2025
5c6d748
realizing that having inputs/outputs methods does make sense, because…
marsipu Jan 19, 2025
10ae568
update pre-commit hook version tags
marsipu Jan 19, 2025
4621866
change default of label_time_course extraction_mode
marsipu Jan 24, 2025
10c625b
connection initializing missing
marsipu Jan 27, 2025
9d0c093
add docformatter
marsipu Jan 27, 2025
f0b3278
remove unnecessary line (test docformatter)
marsipu Jan 27, 2025
6554a0e
test again
marsipu Jan 27, 2025
bd5468f
apply docformatter
marsipu Jan 27, 2025
64a0f6f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 27, 2025
a32cf86
begin transition to remove set_output/set_input
marsipu Jan 27, 2025
0f12154
make main run again
marsipu Jan 27, 2025
f35a316
fix cluttered error messages in console
marsipu Jan 28, 2025
5d28515
fix bugs
marsipu Jan 28, 2025
c48af51
fix bugs
marsipu Jan 28, 2025
91749ef
Merge branch 'main' into nodes
marsipu Jan 29, 2025
fccf3ce
add some directions
marsipu Feb 6, 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
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,10 @@ repos:
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]

# docformatter
- repo: https://github.com/PyCQA/docformatter
rev: eb1df347edd128b30cd3368dddc3aa65edcfac38
hooks:
- id: docformatter
additional_dependencies: [ tomli ]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ The colorpalettes for light and dark theme are inspired from [PyQtDarkTheme](htt
Many ideas and basics for GUI-Programming where taken
from [LearnPyQt](https://www.learnpyqt.com/) and numerous
stackoverflow-questions/solutions.
For the nodes, code and major influences from [NodeGraphQt](https://github.com/jchanvfx/NodeGraphQt) are used.

The development is financially supported
by [Heidelberg University](https://www.uni-heidelberg.de/de/forschung/forschungsprofil/fields-of-focus/field-of-focus-i).
Expand Down
58 changes: 57 additions & 1 deletion mne_pipeline_hd/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import pytest

from mne_pipeline_hd.gui.node.node_viewer import NodeViewer
from mne_pipeline_hd.gui.main_window import MainWindow
from mne_pipeline_hd.pipeline.controller import Controller
from mne_pipeline_hd.pipeline.controller import Controller, NewController
from mne_pipeline_hd.pipeline.pipeline_utils import _set_test_run


Expand All @@ -32,3 +33,58 @@ def main_window(controller, qtbot):
qtbot.addWidget(mw)

return mw


@pytest.fixture
def nodeviewer(qtbot):
viewer = NodeViewer(NewController(), debug_mode=True)
viewer.resize(1000, 1000)
qtbot.addWidget(viewer)
viewer.show()

func_kwargs = {
"ports": [
{
"name": "In1",
"port_type": "in",
"accepted_ports": ["Out1"],
},
{
"name": "In2",
"port_type": "in",
"accepted_ports": ["Out1, Out2"],
},
{
"name": "Out1",
"port_type": "out",
"accepted_ports": ["In1"],
"multi_connection": True,
},
{
"name": "Out2",
"port_type": "out",
"accepted_ports": ["In1", "In2"],
"multi_connection": True,
},
],
"name": "test_func",
"parameters": {
"low_cutoff": {
"alias": "Low-Cutoff",
"gui": "FloatGui",
"default": 0.1,
},
"high_cutoff": {
"alias": "High-Cutoff",
"gui": "FloatGui",
"default": 0.2,
},
},
}
func_node1 = viewer.create_node("FunctionNode", **func_kwargs)
func_node2 = viewer.create_node("FunctionNode", **func_kwargs)
func_node1.output(port_idx=0).connect_to(func_node2.input(port_idx=0))

func_node2.setPos(400, 100)

return viewer
50 changes: 50 additions & 0 deletions mne_pipeline_hd/development/animation_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
import sys
from qtpy.QtWidgets import (
QApplication,
QGraphicsPathItem,
QGraphicsView,
QGraphicsScene,
)
from qtpy.QtGui import QPen, QColor, QPainterPath
from qtpy.QtCore import Qt, QVariantAnimation


class Edge(QGraphicsPathItem):
def __init__(self):
super().__init__()
self.setPen(QPen(Qt.white, 5, Qt.SolidLine))

self.animation = QVariantAnimation()
self.animation.setLoopCount(-1)
self.animation.valueChanged.connect(self.handle_valueChanged)
self.animation.setStartValue(QColor("blue"))
self.animation.setKeyValueAt(0.25, QColor("green"))
self.animation.setKeyValueAt(0.5, QColor("yellow"))
self.animation.setKeyValueAt(0.75, QColor("red"))
self.animation.setEndValue(QColor("blue"))
self.animation.setDuration(2000)

def start_animation(self):
self.animation.start()

def handle_valueChanged(self, value):
pen = self.pen()
pen.setColor(value)
self.setPen(pen)


app = QApplication(sys.argv)
viewer = QGraphicsView()
scene = QGraphicsScene()
viewer.setScene(scene)
edge = Edge()
scene.addItem(edge)
path = QPainterPath()
path.moveTo(0, 0)
path.lineTo(100, 100)
edge.setPath(path)
edge.start_animation()
viewer.show()

sys.exit(app.exec())
8 changes: 8 additions & 0 deletions mne_pipeline_hd/development/development_considerations.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,11 @@ setting should be device/OS-dependent:
2. QSettings(), which is stored by Qt on an OS-depending location and which may
differ between devices/OS. Settings which dependent on the device/OS should
go here (e.g. `n_jobs` or `use_cuda`)

## Nodes
Nodes should improve usability and the representation of the pipeline by the following:
- The order of execution is now clearer and renders the function-dependency considerations obsolete.
- The user can now see the input and output of each function.
- Parameters will now go to each function directly, overview only optional
- Using multiple File-Lists or Projets side-by-side will be more easy to handle.
-
2 changes: 1 addition & 1 deletion mne_pipeline_hd/extra/parameters.csv
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ stc_animation_span;;Inverse;(0,0.5);s;time-span for stc-animation[s];TupleGui;
stc_animation_dilat;;Inverse;20;;time-dilation for stc-animation;IntGui;
target_labels;Target Labels;Inverse;[];;;LabelGui;
label_colors;;Inverse;{};;Set custom colors for labels.;ColorGui;{'keys': 'target_labels', 'none_select':True}
extract_mode;Label-Extraction-Mode;Inverse;auto;;mode for extracting label-time-course from Source-Estimate;ComboGui;{'options': ['auto', 'max', 'mean', 'mean_flip', 'pca_flip']}
extract_mode;Label-Extraction-Mode;Inverse;mean;;mode for extracting label-time-course from Source-Estimate;ComboGui;{'options': ['auto', 'max', 'mean', 'mean_flip', 'pca_flip']}
con_methods;;Connectivity;['coh'];;methods for connectivity;CheckListGui;{'options': ['coh', 'cohy', 'imcoh', 'plv', 'ciplv', 'ppc', 'pli', 'pli2_unbiased', 'wpli', 'wpli2_debiased']}
con_fmin;;Connectivity;30;;lower frequency/frequencies for connectivity;MultiTypeGui;{'type_selection': True, 'types': ['float', 'list']}
con_fmax;;Connectivity;80;;upper frequency/frequencies for connectivity;MultiTypeGui;{'type_selection': True, 'types': ['float', 'list']}
Expand Down
2 changes: 0 additions & 2 deletions mne_pipeline_hd/functions/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,6 @@ def tfr(
powers = list()
itcs = list()

meeg.load_epochs()

# Calculate Time-Frequency for each trial from epochs
# using the selected method
for trial, epoch in meeg.get_trial_epochs():
Expand Down
2 changes: 1 addition & 1 deletion mne_pipeline_hd/functions/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def plot_compare_evokeds(meeg, show_plots):

evokeds = {f"{evoked.comment}={evoked.nave}": evoked for evoked in evokeds}

fig = mne.viz.plot_compare_evokeds(evokeds, show=show_plots)
fig = mne.viz.plot_compare_evokeds(evokeds, title=meeg.name, show=show_plots)

meeg.plot_save("evokeds", subfolder="compare", matplotlib_figure=fig)

Expand Down
58 changes: 25 additions & 33 deletions mne_pipeline_hd/gui/base_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ def _data_changed(self, index, _):
logger().debug(f"{data} changed at {index}")

def content_changed(self):
"""Informs ModelView about external change made in data"""
"""Informs ModelView about external change made in data."""
self.model.layoutChanged.emit()

def replace_data(self, new_data):
"""Replaces model._data with new_data"""
"""Replaces model._data with new_data."""
self.model._data = new_data
self.content_changed()

Expand Down Expand Up @@ -431,30 +431,29 @@ def _checked_changed(self):
logger().debug(f"Changed values: {self.model._checked}")

def replace_checked(self, new_checked):
"""Replaces model._checked with new checked list"""
"""Replaces model._checked with new checked list."""
self.model._checked = new_checked
self.content_changed()

def select_all(self):
"""Select all Items while leaving reference to model._checked intact"""
"""Select all Items while leaving reference to model._checked intact."""
for item in [i for i in self.model._data if i not in self.model._checked]:
self.model._checked.append(item)
# Inform Model about changes
self.content_changed()
self._checked_changed()

def clear_all(self):
"""Deselect all Items while leaving reference
to model._checked intact"""
"""Deselect all Items while leaving reference to model._checked intact."""
self.model._checked.clear()
# Inform Model about changes
self.content_changed()
self._checked_changed()


class CheckDictList(BaseList):
"""A List-Widget to display the items of a list and mark them depending on
their appearance in check_dict.
"""A List-Widget to display the items of a list and mark them depending on their
appearance in check_dict.

Parameters
----------
Expand Down Expand Up @@ -512,15 +511,15 @@ def __init__(
)

def replace_check_dict(self, new_check_dict=None):
"""Replaces model.check_dict with new check_dict"""
"""Replaces model.check_dict with new check_dict."""
if new_check_dict:
self.model._check_dict = new_check_dict
self.content_changed()


class CheckDictEditList(EditList):
"""A List-Widget to display the items of a list and mark them
depending of their appearance in check_dict.
"""A List-Widget to display the items of a list and mark them depending of their
appearance in check_dict.

Parameters
----------
Expand Down Expand Up @@ -589,7 +588,7 @@ def __init__(
)

def replace_check_dict(self, new_check_dict=None):
"""Replaces model.check_dict with new check_dict"""
"""Replaces model.check_dict with new check_dict."""
if new_check_dict:
self.model._check_dict = new_check_dict
self.content_changed()
Expand All @@ -616,10 +615,9 @@ def __init__(
model.layoutChanged.emit()

def get_keyvalue_by_index(self, index):
"""For the given index, make an entry in item_dict with the data
at index as key and a dict as value defining.
if data is key or value and refering to the corresponding key/value
of data depending on its type.
"""For the given index, make an entry in item_dict with the data at index as key
and a dict as value defining. if data is key or value and refering to the
corresponding key/value of data depending on its type.

Parameters
----------
Expand Down Expand Up @@ -683,7 +681,7 @@ def select(self, keys, values, clear_selection=True):


class SimpleDict(BaseDict):
"""A Widget to display a Dictionary
"""A Widget to display a Dictionary.

Parameters
----------
Expand All @@ -699,7 +697,6 @@ class SimpleDict(BaseDict):
Set True to resize the rows to contents.
resize_columns : bool
Set True to resize the columns to contents.

"""

def __init__(
Expand All @@ -723,8 +720,9 @@ def __init__(


# ToDo: DataChanged somehow not emitted when row is removed
# ToDo: Bug when removing multiple rows (fix and add tests)
class EditDict(BaseDict):
"""A Widget to display and edit a Dictionary
"""A Widget to display and edit a Dictionary.

Parameters
----------
Expand All @@ -745,7 +743,6 @@ class EditDict(BaseDict):
Set True to resize the rows to contents.
resize_columns : bool
Set True to resize the columns to contents.

"""

def __init__(
Expand Down Expand Up @@ -831,8 +828,7 @@ def edit_item(self):


class BasePandasTable(Base):
"""
The Base-Class for a table from a pandas DataFrame
"""The Base-Class for a table from a pandas DataFrame.

Parameters
----------
Expand Down Expand Up @@ -870,7 +866,7 @@ def __init__(
model.layoutChanged.emit()

def get_rowcol_by_index(self, index, data_list):
"""Get the data at index and the row and column of this data
"""Get the data at index and the row and column of this data.

Parameters
----------
Expand All @@ -883,7 +879,6 @@ def get_rowcol_by_index(self, index, data_list):
-----
Because this function is supposed to be called consecutively,
the information is stored in an existing list (data_list)

"""
data = self.model.getData(index)
row = self.model.headerData(
Expand Down Expand Up @@ -928,9 +923,7 @@ def _selection_changed(self):
logger().debug(f"Selection changed to {selection_list}")

def select(self, values=None, rows=None, columns=None, clear_selection=True):
"""
Select items in Pandas DataFrame by value
or select complete rows/columns.
"""Select items in Pandas DataFrame by value or select complete rows/columns.

Parameters
----------
Expand All @@ -942,7 +935,6 @@ def select(self, values=None, rows=None, columns=None, clear_selection=True):
Names of columns.
clear_selection: bool | None
Set True if you want to clear the selection before selecting.

"""
indexes = list()
# Get indexes for matching items in pd_data
Expand Down Expand Up @@ -980,7 +972,7 @@ def select(self, values=None, rows=None, columns=None, clear_selection=True):


class SimplePandasTable(BasePandasTable):
"""A Widget to display a pandas DataFrame
"""A Widget to display a pandas DataFrame.

Parameters
----------
Expand Down Expand Up @@ -1024,7 +1016,7 @@ def __init__(


class EditPandasTable(BasePandasTable):
"""A Widget to display and edit a pandas DataFrame
"""A Widget to display and edit a pandas DataFrame.

Parameters
----------
Expand Down Expand Up @@ -1148,8 +1140,8 @@ def init_ui(self):
self.setLayout(layout)

def update_data(self):
"""Has to be called, when model._data is rereferenced
by for example add_row to keep external data updated.
"""Has to be called, when model._data is rereferenced by for example add_row to
keep external data updated.

Returns
-------
Expand Down Expand Up @@ -1319,7 +1311,7 @@ def closeEvent(self, event):


class AssignWidget(QWidget):
""" """
""""""

def __init__(
self,
Expand Down
Loading
Loading