nucleuskit-toolkit 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. nucleuskit_toolkit/__init__.py +3 -0
  2. nucleuskit_toolkit/__main__.py +60 -0
  3. nucleuskit_toolkit/app.py +34 -0
  4. nucleuskit_toolkit/camera/__init__.py +9 -0
  5. nucleuskit_toolkit/camera/processing.py +374 -0
  6. nucleuskit_toolkit/config.py +86 -0
  7. nucleuskit_toolkit/hermes/__init__.py +0 -0
  8. nucleuskit_toolkit/hermes/constants.py +77 -0
  9. nucleuskit_toolkit/hermes/realtime/__init__.py +8 -0
  10. nucleuskit_toolkit/hermes/realtime/proxy.py +652 -0
  11. nucleuskit_toolkit/logging_utils.py +68 -0
  12. nucleuskit_toolkit/mqtt/__init__.py +5 -0
  13. nucleuskit_toolkit/mqtt/mqtt_controller.py +256 -0
  14. nucleuskit_toolkit/pipeline.py +194 -0
  15. nucleuskit_toolkit/position/__init__.py +5 -0
  16. nucleuskit_toolkit/position/gps_processor.py +91 -0
  17. nucleuskit_toolkit/session_job.py +28 -0
  18. nucleuskit_toolkit/shimmer/__init__.py +5 -0
  19. nucleuskit_toolkit/shimmer/constants.py +32 -0
  20. nucleuskit_toolkit/shimmer/realtime/__init__.py +5 -0
  21. nucleuskit_toolkit/shimmer/realtime/proxy.py +279 -0
  22. nucleuskit_toolkit/tools/__init__.py +1 -0
  23. nucleuskit_toolkit/tools/eeg_regression.py +91 -0
  24. nucleuskit_toolkit/tools/recording_merger.py +437 -0
  25. nucleuskit_toolkit/ui/__init__.py +1 -0
  26. nucleuskit_toolkit/ui/icons.py +165 -0
  27. nucleuskit_toolkit/ui/main_window.py +465 -0
  28. nucleuskit_toolkit/ui/offline_job.py +144 -0
  29. nucleuskit_toolkit/ui/pages/__init__.py +1 -0
  30. nucleuskit_toolkit/ui/pages/about_page.py +88 -0
  31. nucleuskit_toolkit/ui/pages/channel_fixer_page.py +205 -0
  32. nucleuskit_toolkit/ui/pages/channel_gain_page.py +362 -0
  33. nucleuskit_toolkit/ui/pages/eeg_regression_page.py +385 -0
  34. nucleuskit_toolkit/ui/pages/mqtt_controller_page.py +394 -0
  35. nucleuskit_toolkit/ui/pages/offline_page.py +754 -0
  36. nucleuskit_toolkit/ui/pages/playback_page.py +1524 -0
  37. nucleuskit_toolkit/ui/pages/ppg_fixer_page.py +199 -0
  38. nucleuskit_toolkit/ui/pages/realtime_viewer_page.py +954 -0
  39. nucleuskit_toolkit/ui/pages/recording_merge_page.py +395 -0
  40. nucleuskit_toolkit/ui/pages/revert_original_page.py +128 -0
  41. nucleuskit_toolkit/ui/pages/settings_page.py +69 -0
  42. nucleuskit_toolkit/ui/pages/tools_menu_page.py +42 -0
  43. nucleuskit_toolkit/ui/plot_style.py +155 -0
  44. nucleuskit_toolkit/ui/realtime_eeg_plot.py +244 -0
  45. nucleuskit_toolkit/ui/realtime_hermes_motion_plot.py +162 -0
  46. nucleuskit_toolkit/ui/realtime_nlms_config_dialog.py +101 -0
  47. nucleuskit_toolkit/ui/realtime_shimmer_plot.py +135 -0
  48. nucleuskit_toolkit/ui/resources/__init__.py +1 -0
  49. nucleuskit_toolkit/ui/resources/branding/__init__.py +1 -0
  50. nucleuskit_toolkit/ui/resources/branding/logo.png +0 -0
  51. nucleuskit_toolkit/ui/session_inspect.py +226 -0
  52. nucleuskit_toolkit/ui/theme.py +330 -0
  53. nucleuskit_toolkit/ui/widgets.py +190 -0
  54. nucleuskit_toolkit-2.2.0.dist-info/METADATA +351 -0
  55. nucleuskit_toolkit-2.2.0.dist-info/RECORD +59 -0
  56. nucleuskit_toolkit-2.2.0.dist-info/WHEEL +5 -0
  57. nucleuskit_toolkit-2.2.0.dist-info/entry_points.txt +2 -0
  58. nucleuskit_toolkit-2.2.0.dist-info/licenses/LICENSE +675 -0
  59. nucleuskit_toolkit-2.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,199 @@
1
+ """PPG Fixer: artifact rejection via squared z-score outlier detection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import queue
6
+ import sys
7
+ import traceback
8
+
9
+ from PySide6.QtCore import QObject, QThread, QTimer, Signal, Slot
10
+ from PySide6.QtGui import QTextCursor
11
+ from PySide6.QtWidgets import (
12
+ QFileDialog,
13
+ QGroupBox,
14
+ QHBoxLayout,
15
+ QLineEdit,
16
+ QMessageBox,
17
+ QPlainTextEdit,
18
+ QProgressBar,
19
+ QPushButton,
20
+ QVBoxLayout,
21
+ QWidget,
22
+ )
23
+ from reak_core.tools import fix_ppg_session
24
+
25
+ from nucleuskit_toolkit.ui.offline_job import QueueTextWriter, ppg_fixer_preflight
26
+
27
+
28
+ class _PpgFixerWorker(QObject):
29
+ finished_ok = Signal()
30
+ finished_err = Signal(str)
31
+
32
+ def __init__(self, folder: str, log_queue: queue.SimpleQueue[str]) -> None:
33
+ super().__init__()
34
+ self._folder = folder
35
+ self._log_queue = log_queue
36
+
37
+ @Slot()
38
+ def run(self) -> None:
39
+ err: str | None = None
40
+ writer = QueueTextWriter(self._log_queue)
41
+ old_out, old_err = sys.stdout, sys.stderr
42
+ try:
43
+ sys.stdout = writer
44
+ sys.stderr = writer
45
+ fix_ppg_session(self._folder)
46
+ except BaseException:
47
+ err = traceback.format_exc()
48
+ self._log_queue.put(err)
49
+ finally:
50
+ sys.stdout = old_out
51
+ sys.stderr = old_err
52
+
53
+ if err:
54
+ self.finished_err.emit("Run finished with errors (see log).")
55
+ else:
56
+ self.finished_ok.emit()
57
+
58
+
59
+ class PpgFixerPage(QWidget):
60
+ go_tools_menu = Signal()
61
+ processing_changed = Signal(bool)
62
+
63
+ def __init__(self, parent: QWidget | None = None) -> None:
64
+ super().__init__(parent)
65
+ self._log_queue: queue.SimpleQueue[str] = queue.SimpleQueue()
66
+ self._thread: QThread | None = None
67
+ self._worker: _PpgFixerWorker | None = None
68
+
69
+ # ── top bar ──────────────────────────────────────────────────
70
+ self._back = QPushButton("Back")
71
+ self._back.setProperty("secondary", True)
72
+ self._back.clicked.connect(self.go_tools_menu.emit)
73
+
74
+ top = QHBoxLayout()
75
+ top.addStretch(1)
76
+ top.addWidget(self._back)
77
+
78
+ # ── session folder selector ───────────────────────────────────
79
+ self._session = QLineEdit()
80
+ self._session.setPlaceholderText("Session folder path…")
81
+ browse = QPushButton("Browse…")
82
+ browse.clicked.connect(self._browse_session)
83
+
84
+ sess_row = QHBoxLayout()
85
+ sess_row.addWidget(self._session, 1)
86
+ sess_row.addWidget(browse)
87
+
88
+ session_box = QGroupBox(
89
+ "Session (must contain features/ppg/ppg_resampled.csv)"
90
+ )
91
+ sg = QVBoxLayout(session_box)
92
+ sg.addLayout(sess_row)
93
+
94
+ # ── actions ───────────────────────────────────────────────────
95
+ self._run = QPushButton("Run PPG Fixer")
96
+ self._run.clicked.connect(self._run_clicked)
97
+
98
+ self._progress = QProgressBar()
99
+ self._progress.setRange(0, 0)
100
+ self._progress.setVisible(False)
101
+
102
+ actions = QHBoxLayout()
103
+ actions.addWidget(self._run)
104
+ actions.addWidget(self._progress, 1)
105
+
106
+ # ── log ───────────────────────────────────────────────────────
107
+ self._log = QPlainTextEdit()
108
+ self._log.setReadOnly(True)
109
+
110
+ log_box = QGroupBox("Log")
111
+ lg = QVBoxLayout(log_box)
112
+ lg.addWidget(self._log)
113
+
114
+ # ── assemble ─────────────────────────────────────────────────
115
+ layout = QVBoxLayout(self)
116
+ layout.addLayout(top)
117
+ layout.addWidget(session_box)
118
+ layout.addLayout(actions)
119
+ layout.addWidget(log_box, 1)
120
+
121
+ self._poll_timer = QTimer(self)
122
+ self._poll_timer.timeout.connect(self._drain_log_queue)
123
+ self._poll_timer.start(120)
124
+
125
+ # ── private helpers ───────────────────────────────────────────────
126
+
127
+ def _browse_session(self) -> None:
128
+ path = QFileDialog.getExistingDirectory(self, "Select session folder")
129
+ if path:
130
+ self._session.setText(path)
131
+
132
+ def _set_processing(self, running: bool) -> None:
133
+ self._back.setEnabled(not running)
134
+ self._run.setEnabled(not running)
135
+ self._session.setEnabled(not running)
136
+ self._progress.setVisible(running)
137
+ self.processing_changed.emit(running)
138
+
139
+ def _run_clicked(self) -> None:
140
+ folder = self._session.text().strip()
141
+ err = ppg_fixer_preflight(folder)
142
+ if err:
143
+ QMessageBox.warning(self, "Session", err)
144
+ return
145
+ if self._thread is not None and self._thread.isRunning():
146
+ QMessageBox.information(self, "Busy", "A run is already in progress.")
147
+ return
148
+
149
+ self._set_processing(True)
150
+ self._insert_log(f"\n--- Starting PPG Fixer: {folder!r} ---\n")
151
+
152
+ thread = QThread()
153
+ worker = _PpgFixerWorker(folder, self._log_queue)
154
+ worker.moveToThread(thread)
155
+
156
+ thread.started.connect(worker.run)
157
+ worker.finished_ok.connect(self._on_finished_ok)
158
+ worker.finished_err.connect(self._on_finished_err)
159
+ worker.finished_ok.connect(thread.quit)
160
+ worker.finished_err.connect(thread.quit)
161
+ thread.finished.connect(worker.deleteLater)
162
+ thread.finished.connect(thread.deleteLater)
163
+ thread.finished.connect(self._clear_thread_ref)
164
+
165
+ self._thread = thread
166
+ self._worker = worker
167
+ thread.start()
168
+
169
+ def _clear_thread_ref(self) -> None:
170
+ self._thread = None
171
+ self._worker = None
172
+
173
+ def _on_finished_ok(self) -> None:
174
+ self._set_processing(False)
175
+ QMessageBox.information(
176
+ self,
177
+ "PPG Fixer",
178
+ "Done.\n\n"
179
+ "Artifacts removed, features/ppg/ppg_rejected.png saved, and "
180
+ "results/HeartDynamics.csv recomputed.",
181
+ )
182
+
183
+ def _on_finished_err(self, msg: str) -> None:
184
+ self._set_processing(False)
185
+ QMessageBox.critical(self, "PPG Fixer", msg)
186
+
187
+ def _insert_log(self, text: str) -> None:
188
+ self._log.moveCursor(QTextCursor.End)
189
+ self._log.insertPlainText(text)
190
+ sb = self._log.verticalScrollBar()
191
+ sb.setValue(sb.maximum())
192
+
193
+ def _drain_log_queue(self) -> None:
194
+ try:
195
+ while True:
196
+ chunk = self._log_queue.get_nowait()
197
+ self._insert_log(chunk)
198
+ except queue.Empty:
199
+ pass