mesofield 0.3.2b0__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 (111) hide show
  1. docs/_static/custom.css +40 -0
  2. docs/_static/favicon.png +0 -0
  3. docs/_static/logo.png +0 -0
  4. docs/api/index.md +70 -0
  5. docs/conf.py +200 -0
  6. docs/developer_guide.md +303 -0
  7. docs/index.md +25 -0
  8. docs/tutorial.md +4 -0
  9. docs/user_guide.md +172 -0
  10. examples/teensy_pulse_generator.py +320 -0
  11. experiments/pipeline_demo/experiment.json +24 -0
  12. experiments/pipeline_demo/hardware.yaml +23 -0
  13. experiments/pipeline_demo/procedure.py +50 -0
  14. experiments/two_cam_demo/experiment.json +24 -0
  15. experiments/two_cam_demo/hardware.yaml +58 -0
  16. experiments/two_cam_demo/load_dataset.py +213 -0
  17. experiments/two_cam_demo/procedure.py +87 -0
  18. external/video-codecs/openh264-1.8.0-win64.dll +0 -0
  19. mesofield/__init__.py +45 -0
  20. mesofield/__main__.py +11 -0
  21. mesofield/_version.py +24 -0
  22. mesofield/base.py +750 -0
  23. mesofield/cli/__init__.py +57 -0
  24. mesofield/cli/_richhelp.py +100 -0
  25. mesofield/cli/acquire.py +254 -0
  26. mesofield/cli/datakit.py +165 -0
  27. mesofield/cli/process.py +376 -0
  28. mesofield/cli/rig.py +108 -0
  29. mesofield/cli/tools.py +347 -0
  30. mesofield/config.py +751 -0
  31. mesofield/data/__init__.py +23 -0
  32. mesofield/data/batch.py +633 -0
  33. mesofield/data/manager.py +388 -0
  34. mesofield/data/writer.py +289 -0
  35. mesofield/datakit/__init__.py +44 -0
  36. mesofield/datakit/__main__.py +35 -0
  37. mesofield/datakit/_utils/_logger.py +5 -0
  38. mesofield/datakit/_version.py +141 -0
  39. mesofield/datakit/config.py +50 -0
  40. mesofield/datakit/core.py +783 -0
  41. mesofield/datakit/datamodel.py +200 -0
  42. mesofield/datakit/discover.py +124 -0
  43. mesofield/datakit/explore.py +651 -0
  44. mesofield/datakit/notebooks/pupil_dlc.ipynb +2445 -0
  45. mesofield/datakit/profile.py +535 -0
  46. mesofield/datakit/shell.py +83 -0
  47. mesofield/datakit/sources/__init__.py +65 -0
  48. mesofield/datakit/sources/analysis/mesomap.py +194 -0
  49. mesofield/datakit/sources/analysis/mesoscope.py +77 -0
  50. mesofield/datakit/sources/analysis/pupil.py +246 -0
  51. mesofield/datakit/sources/behavior/__init__.py +0 -0
  52. mesofield/datakit/sources/behavior/dataqueue.py +281 -0
  53. mesofield/datakit/sources/behavior/psychopy.py +364 -0
  54. mesofield/datakit/sources/behavior/treadmill.py +323 -0
  55. mesofield/datakit/sources/behavior/wheel.py +277 -0
  56. mesofield/datakit/sources/camera/mesoscope.py +32 -0
  57. mesofield/datakit/sources/camera/metadata_json.py +130 -0
  58. mesofield/datakit/sources/camera/pupil.py +28 -0
  59. mesofield/datakit/sources/camera/suite2p.py +547 -0
  60. mesofield/datakit/sources/register.py +204 -0
  61. mesofield/datakit/sources/session/config.py +130 -0
  62. mesofield/datakit/sources/session/notes.py +63 -0
  63. mesofield/datakit/sources/session/timestamps.py +58 -0
  64. mesofield/datakit/timeline.py +306 -0
  65. mesofield/devices/__init__.py +42 -0
  66. mesofield/devices/base.py +498 -0
  67. mesofield/devices/base_camera.py +295 -0
  68. mesofield/devices/cameras.py +740 -0
  69. mesofield/devices/daq.py +151 -0
  70. mesofield/devices/encoder.py +384 -0
  71. mesofield/devices/mocks.py +275 -0
  72. mesofield/devices/psychopy_device.py +455 -0
  73. mesofield/devices/subprocesses/__init__.py +0 -0
  74. mesofield/devices/subprocesses/psychopy.py +133 -0
  75. mesofield/devices/treadmill.py +318 -0
  76. mesofield/engines.py +380 -0
  77. mesofield/gui/Mesofield_icon.png +0 -0
  78. mesofield/gui/__init__.py +76 -0
  79. mesofield/gui/config_wizard.py +724 -0
  80. mesofield/gui/controller.py +535 -0
  81. mesofield/gui/dynamic_controller.py +78 -0
  82. mesofield/gui/maingui.py +427 -0
  83. mesofield/gui/mdagui.py +285 -0
  84. mesofield/gui/qt_device_adapter.py +109 -0
  85. mesofield/gui/speedplotter.py +152 -0
  86. mesofield/gui/theme.py +445 -0
  87. mesofield/gui/tiff_viewer.py +1050 -0
  88. mesofield/gui/viewer.py +691 -0
  89. mesofield/hardware.py +549 -0
  90. mesofield/playback.py +1298 -0
  91. mesofield/processing/__init__.py +12 -0
  92. mesofield/processing/runner.py +237 -0
  93. mesofield/processors/__init__.py +13 -0
  94. mesofield/processors/base.py +287 -0
  95. mesofield/processors/frame_mean.py +19 -0
  96. mesofield/protocols.py +378 -0
  97. mesofield/scaffold/__init__.py +34 -0
  98. mesofield/scaffold/experiment.py +400 -0
  99. mesofield/scaffold/rigs.py +121 -0
  100. mesofield/signals.py +85 -0
  101. mesofield/utils/__init__.py +0 -0
  102. mesofield/utils/_logger.py +156 -0
  103. mesofield/utils/retrofit.py +309 -0
  104. mesofield/utils/utils.py +217 -0
  105. mesofield-0.3.2b0.dist-info/METADATA +178 -0
  106. mesofield-0.3.2b0.dist-info/RECORD +111 -0
  107. mesofield-0.3.2b0.dist-info/WHEEL +5 -0
  108. mesofield-0.3.2b0.dist-info/entry_points.txt +2 -0
  109. mesofield-0.3.2b0.dist-info/licenses/LICENSE +21 -0
  110. mesofield-0.3.2b0.dist-info/top_level.txt +6 -0
  111. scripts/bench_frame_processor.py +103 -0
mesofield/gui/theme.py ADDED
@@ -0,0 +1,445 @@
1
+ """Mesofield GUI theme — "Control Panel / Net-Runner" design language.
2
+
3
+ A single source of truth for the application's dark-mode look. The aesthetic is
4
+ a mechanical control-system panel with restrained net-runner cues: deep gunmetal
5
+ surfaces, hairline borders, square-ish corners, and a muted phosphor-green accent
6
+ (with a brighter phosphor reserved for active/recording states).
7
+
8
+ Usage (call once, right after creating the QApplication)::
9
+
10
+ from mesofield.gui import theme
11
+ app = QApplication([])
12
+ theme.apply_theme(app)
13
+
14
+ The module is intentionally dependency-light (only PyQt6) and cross-platform:
15
+ it relies solely on QSS + QPalette + web-safe font *stacks*, so it renders
16
+ consistently on macOS (Cocoa) and Windows 11.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass
21
+
22
+ from PyQt6.QtGui import QColor, QPalette
23
+ from PyQt6.QtWidgets import QApplication
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Palette
28
+ # ---------------------------------------------------------------------------
29
+ @dataclass(frozen=True)
30
+ class Palette:
31
+ """Canonical color tokens for the Mesofield theme."""
32
+
33
+ BG: str = "#0e1216" # app background (near-black blue-grey)
34
+ PANEL: str = "#161b22" # cards / group boxes / inputs
35
+ PANEL_HI: str = "#1d242d" # hover / alternating rows
36
+ BORDER: str = "#2b333d" # hairline mechanical borders
37
+ BORDER_HI: str = "#3a4450" # focused / hovered borders
38
+ TEXT: str = "#c8d0d8" # primary text
39
+ TEXT_DIM: str = "#7d8893" # secondary / disabled text
40
+ ACCENT: str = "#4fd6a0" # muted phosphor (primary accent)
41
+ ACCENT_HI: str = "#39FF14" # bright phosphor (active / recording only)
42
+ WARN: str = "#e0a23c" # amber (status warnings)
43
+ DANGER: str = "#e0574f" # red (abort / record)
44
+
45
+
46
+ PALETTE = Palette()
47
+
48
+ # Convenience module-level aliases (so callers can do ``theme.ACCENT``).
49
+ BG = PALETTE.BG
50
+ PANEL = PALETTE.PANEL
51
+ PANEL_HI = PALETTE.PANEL_HI
52
+ BORDER = PALETTE.BORDER
53
+ BORDER_HI = PALETTE.BORDER_HI
54
+ TEXT = PALETTE.TEXT
55
+ TEXT_DIM = PALETTE.TEXT_DIM
56
+ ACCENT = PALETTE.ACCENT
57
+ ACCENT_HI = PALETTE.ACCENT_HI
58
+ WARN = PALETTE.WARN
59
+ DANGER = PALETTE.DANGER
60
+
61
+ # Monospace stack for data / numeric / terminal surfaces only.
62
+ MONO_FONT = '"JetBrains Mono", "Cascadia Mono", "SF Mono", "Menlo", "Consolas", monospace'
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Stylesheet
67
+ # ---------------------------------------------------------------------------
68
+ STYLESHEET = f"""
69
+ /* ===================== Base surfaces ===================== */
70
+ QWidget {{
71
+ background-color: {BG};
72
+ color: {TEXT};
73
+ selection-background-color: {ACCENT};
74
+ selection-color: {BG};
75
+ }}
76
+ QMainWindow, QDialog {{
77
+ background-color: {BG};
78
+ }}
79
+ QWidget:disabled {{
80
+ color: {TEXT_DIM};
81
+ }}
82
+
83
+ /* ===================== Toolbar ===================== */
84
+ QToolBar {{
85
+ background-color: {PANEL};
86
+ border: none;
87
+ border-bottom: 1px solid {BORDER};
88
+ spacing: 4px;
89
+ padding: 3px;
90
+ }}
91
+ QToolBar QToolButton {{
92
+ background-color: transparent;
93
+ color: {TEXT};
94
+ border: 1px solid transparent;
95
+ border-radius: 3px;
96
+ padding: 4px 8px;
97
+ }}
98
+ QToolBar QToolButton:hover {{
99
+ background-color: {PANEL_HI};
100
+ border: 1px solid {BORDER_HI};
101
+ }}
102
+ QToolBar QToolButton:pressed {{
103
+ background-color: {BG};
104
+ }}
105
+
106
+ /* ===================== Menus ===================== */
107
+ QMenuBar {{
108
+ background-color: {PANEL};
109
+ color: {TEXT};
110
+ border-bottom: 1px solid {BORDER};
111
+ }}
112
+ QMenuBar::item {{
113
+ background: transparent;
114
+ padding: 4px 10px;
115
+ }}
116
+ QMenuBar::item:selected {{
117
+ background-color: {PANEL_HI};
118
+ }}
119
+ QMenu {{
120
+ background-color: {PANEL};
121
+ color: {TEXT};
122
+ border: 1px solid {BORDER};
123
+ }}
124
+ QMenu::item {{
125
+ padding: 5px 22px;
126
+ }}
127
+ QMenu::item:selected {{
128
+ background-color: {PANEL_HI};
129
+ color: {ACCENT};
130
+ }}
131
+ QMenu::separator {{
132
+ height: 1px;
133
+ background: {BORDER};
134
+ margin: 4px 6px;
135
+ }}
136
+
137
+ /* ===================== Tabs ===================== */
138
+ QTabWidget::pane {{
139
+ background-color: {PANEL};
140
+ border: 1px solid {BORDER};
141
+ top: -1px;
142
+ }}
143
+ QTabBar::tab {{
144
+ background-color: {BG};
145
+ color: {TEXT_DIM};
146
+ border: 1px solid {BORDER};
147
+ border-bottom: none;
148
+ padding: 6px 14px;
149
+ margin-right: 1px;
150
+ }}
151
+ QTabBar::tab:hover {{
152
+ color: {TEXT};
153
+ background-color: {PANEL_HI};
154
+ }}
155
+ QTabBar::tab:selected {{
156
+ background-color: {PANEL};
157
+ color: {ACCENT};
158
+ border-top: 2px solid {ACCENT};
159
+ }}
160
+
161
+ /* ===================== Group boxes ===================== */
162
+ QGroupBox {{
163
+ background-color: {PANEL};
164
+ border: 1px solid {BORDER};
165
+ border-radius: 3px;
166
+ margin-top: 14px;
167
+ padding: 10px 8px 8px 8px;
168
+ }}
169
+ QGroupBox::title {{
170
+ subcontrol-origin: margin;
171
+ subcontrol-position: top left;
172
+ left: 8px;
173
+ padding: 0 5px;
174
+ color: {ACCENT};
175
+ text-transform: uppercase;
176
+ letter-spacing: 1px;
177
+ }}
178
+
179
+ /* ===================== Buttons ===================== */
180
+ QPushButton {{
181
+ background-color: {PANEL};
182
+ color: {TEXT};
183
+ border: 1px solid {BORDER};
184
+ border-radius: 3px;
185
+ padding: 5px 14px;
186
+ }}
187
+ QPushButton:hover {{
188
+ background-color: {PANEL_HI};
189
+ border: 1px solid {ACCENT};
190
+ }}
191
+ QPushButton:pressed {{
192
+ background-color: {BG};
193
+ }}
194
+ QPushButton:focus {{
195
+ border: 1px solid {ACCENT};
196
+ }}
197
+ QPushButton:disabled {{
198
+ background-color: {PANEL};
199
+ color: {TEXT_DIM};
200
+ border: 1px solid {BORDER};
201
+ }}
202
+ QPushButton:checked {{
203
+ border: 1px solid {ACCENT};
204
+ color: {ACCENT};
205
+ }}
206
+
207
+ /* ===================== Inputs ===================== */
208
+ QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit, QAbstractSpinBox {{
209
+ background-color: {BG};
210
+ color: {TEXT};
211
+ border: 1px solid {BORDER};
212
+ border-radius: 3px;
213
+ padding: 4px 6px;
214
+ selection-background-color: {ACCENT};
215
+ selection-color: {BG};
216
+ }}
217
+ QLineEdit:hover, QComboBox:hover, QSpinBox:hover, QDoubleSpinBox:hover, QAbstractSpinBox:hover {{
218
+ border: 1px solid {BORDER_HI};
219
+ }}
220
+ QLineEdit:focus, QComboBox:focus, QSpinBox:focus, QDoubleSpinBox:focus,
221
+ QPlainTextEdit:focus, QTextEdit:focus, QAbstractSpinBox:focus {{
222
+ border: 1px solid {ACCENT};
223
+ border-bottom: 2px solid {ACCENT};
224
+ }}
225
+ QLineEdit:disabled, QComboBox:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled {{
226
+ color: {TEXT_DIM};
227
+ background-color: {PANEL};
228
+ }}
229
+ QComboBox::drop-down {{
230
+ border: none;
231
+ width: 18px;
232
+ }}
233
+ QComboBox QAbstractItemView {{
234
+ background-color: {PANEL};
235
+ color: {TEXT};
236
+ border: 1px solid {BORDER};
237
+ selection-background-color: {PANEL_HI};
238
+ selection-color: {ACCENT};
239
+ outline: none;
240
+ }}
241
+
242
+ /* ===================== Check / radio ===================== */
243
+ QCheckBox, QRadioButton {{
244
+ background: transparent;
245
+ color: {TEXT};
246
+ spacing: 6px;
247
+ }}
248
+ QCheckBox::indicator, QRadioButton::indicator {{
249
+ width: 14px;
250
+ height: 14px;
251
+ background-color: {BG};
252
+ border: 1px solid {BORDER_HI};
253
+ }}
254
+ QCheckBox::indicator {{
255
+ border-radius: 2px;
256
+ }}
257
+ QRadioButton::indicator {{
258
+ border-radius: 7px;
259
+ }}
260
+ QCheckBox::indicator:hover, QRadioButton::indicator:hover {{
261
+ border: 1px solid {ACCENT};
262
+ }}
263
+ QCheckBox::indicator:checked, QRadioButton::indicator:checked {{
264
+ background-color: {ACCENT};
265
+ border: 1px solid {ACCENT};
266
+ }}
267
+
268
+ /* ===================== Tables / trees ===================== */
269
+ QTableView, QTreeView, QTableWidget, QListView {{
270
+ background-color: {PANEL};
271
+ alternate-background-color: {PANEL_HI};
272
+ color: {TEXT};
273
+ gridline-color: {BORDER};
274
+ border: 1px solid {BORDER};
275
+ selection-background-color: {PANEL_HI};
276
+ selection-color: {ACCENT};
277
+ outline: none;
278
+ }}
279
+ QTableView::item:selected, QTreeView::item:selected, QListView::item:selected {{
280
+ background-color: {PANEL_HI};
281
+ color: {ACCENT};
282
+ }}
283
+ QHeaderView::section {{
284
+ background-color: {BG};
285
+ color: {TEXT_DIM};
286
+ border: none;
287
+ border-right: 1px solid {BORDER};
288
+ border-bottom: 1px solid {BORDER};
289
+ padding: 5px 8px;
290
+ text-transform: uppercase;
291
+ letter-spacing: 1px;
292
+ }}
293
+ QTableCornerButton::section {{
294
+ background-color: {BG};
295
+ border: 1px solid {BORDER};
296
+ }}
297
+
298
+ /* ===================== Scrollbars (slim, mechanical) ===================== */
299
+ QScrollBar:vertical {{
300
+ background: {BG};
301
+ width: 11px;
302
+ margin: 0;
303
+ }}
304
+ QScrollBar:horizontal {{
305
+ background: {BG};
306
+ height: 11px;
307
+ margin: 0;
308
+ }}
309
+ QScrollBar::handle:vertical, QScrollBar::handle:horizontal {{
310
+ background: {BORDER};
311
+ border-radius: 2px;
312
+ min-height: 24px;
313
+ min-width: 24px;
314
+ margin: 2px;
315
+ }}
316
+ QScrollBar::handle:hover {{
317
+ background: {BORDER_HI};
318
+ }}
319
+ QScrollBar::add-line, QScrollBar::sub-line {{
320
+ height: 0; width: 0;
321
+ }}
322
+ QScrollBar::add-page, QScrollBar::sub-page {{
323
+ background: transparent;
324
+ }}
325
+
326
+ /* ===================== Splitter ===================== */
327
+ QSplitter::handle {{
328
+ background-color: {BORDER};
329
+ }}
330
+ QSplitter::handle:hover {{
331
+ background-color: {ACCENT};
332
+ }}
333
+
334
+ /* ===================== Misc ===================== */
335
+ QToolTip {{
336
+ background-color: {PANEL};
337
+ color: {TEXT};
338
+ border: 1px solid {ACCENT};
339
+ padding: 4px 6px;
340
+ }}
341
+ QProgressBar {{
342
+ background-color: {BG};
343
+ border: 1px solid {BORDER};
344
+ border-radius: 3px;
345
+ text-align: center;
346
+ color: {TEXT};
347
+ }}
348
+ QProgressBar::chunk {{
349
+ background-color: {ACCENT};
350
+ }}
351
+ QLabel {{
352
+ background: transparent;
353
+ }}
354
+ """
355
+
356
+
357
+ # ---------------------------------------------------------------------------
358
+ # Helpers for special widget states
359
+ # ---------------------------------------------------------------------------
360
+ def record_button_qss() -> str:
361
+ """QSS for the record button: idle gunmetal → armed bright phosphor.
362
+
363
+ Use ``setProperty("armed", True)`` + ``style().polish()`` to switch the
364
+ button into its active (bright phosphor) recording state.
365
+ """
366
+ return f"""
367
+ QPushButton {{
368
+ background-color: {PANEL};
369
+ color: {TEXT};
370
+ border: 1px solid {DANGER};
371
+ border-radius: 3px;
372
+ padding: 6px 12px;
373
+ }}
374
+ QPushButton:hover {{
375
+ background-color: {PANEL_HI};
376
+ border: 1px solid {ACCENT_HI};
377
+ }}
378
+ QPushButton:pressed {{
379
+ background-color: {BG};
380
+ }}
381
+ QPushButton[armed="true"] {{
382
+ background-color: {DANGER};
383
+ color: {BG};
384
+ border: 1px solid {ACCENT_HI};
385
+ }}
386
+ """
387
+
388
+
389
+ def terminal_qss() -> str:
390
+ """QSS for the embedded IPython console (monospace phosphor-on-panel)."""
391
+ return f"""
392
+ RichJupyterWidget {{
393
+ background-color: {PANEL};
394
+ }}
395
+ QPlainTextEdit, QTextEdit {{
396
+ background-color: {PANEL};
397
+ color: {ACCENT_HI};
398
+ font-family: {MONO_FONT};
399
+ }}
400
+ QLabel {{
401
+ color: {ACCENT_HI};
402
+ }}
403
+ """
404
+
405
+
406
+ # ---------------------------------------------------------------------------
407
+ # Application entry point
408
+ # ---------------------------------------------------------------------------
409
+ def _build_palette() -> QPalette:
410
+ """Construct a dark QPalette so native / unstyled widgets render dark.
411
+
412
+ This also guards against the known Windows 11 dark-mode issue where Qt
413
+ paints dark backgrounds on native controls but leaves text dark.
414
+ """
415
+ p = QPalette()
416
+ p.setColor(QPalette.ColorRole.Window, QColor(BG))
417
+ p.setColor(QPalette.ColorRole.WindowText, QColor(TEXT))
418
+ p.setColor(QPalette.ColorRole.Base, QColor(BG))
419
+ p.setColor(QPalette.ColorRole.AlternateBase, QColor(PANEL_HI))
420
+ p.setColor(QPalette.ColorRole.Text, QColor(TEXT))
421
+ p.setColor(QPalette.ColorRole.PlaceholderText, QColor(TEXT_DIM))
422
+ p.setColor(QPalette.ColorRole.Button, QColor(PANEL))
423
+ p.setColor(QPalette.ColorRole.ButtonText, QColor(TEXT))
424
+ p.setColor(QPalette.ColorRole.BrightText, QColor(ACCENT_HI))
425
+ p.setColor(QPalette.ColorRole.Highlight, QColor(ACCENT))
426
+ p.setColor(QPalette.ColorRole.HighlightedText, QColor(BG))
427
+ p.setColor(QPalette.ColorRole.ToolTipBase, QColor(PANEL))
428
+ p.setColor(QPalette.ColorRole.ToolTipText, QColor(TEXT))
429
+ p.setColor(QPalette.ColorRole.Link, QColor(ACCENT))
430
+
431
+ # Disabled-state roles.
432
+ disabled = QColor(TEXT_DIM)
433
+ for role in (
434
+ QPalette.ColorRole.WindowText,
435
+ QPalette.ColorRole.Text,
436
+ QPalette.ColorRole.ButtonText,
437
+ ):
438
+ p.setColor(QPalette.ColorGroup.Disabled, role, disabled)
439
+ return p
440
+
441
+
442
+ def apply_theme(app: QApplication) -> None:
443
+ """Apply the Mesofield dark theme to *app* (palette first, then QSS)."""
444
+ app.setPalette(_build_palette())
445
+ app.setStyleSheet(STYLESHEET)