pineuro 1.0.0__tar.gz

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 (99) hide show
  1. pineuro-1.0.0/LICENSE +13 -0
  2. pineuro-1.0.0/PKG-INFO +121 -0
  3. pineuro-1.0.0/README.md +72 -0
  4. pineuro-1.0.0/pyproject.toml +145 -0
  5. pineuro-1.0.0/setup.cfg +4 -0
  6. pineuro-1.0.0/src/pineuro/__init__.py +284 -0
  7. pineuro-1.0.0/src/pineuro/atlas_registry.py +49 -0
  8. pineuro-1.0.0/src/pineuro/backup.py +210 -0
  9. pineuro-1.0.0/src/pineuro/collection_qc.py +377 -0
  10. pineuro-1.0.0/src/pineuro/config.py +90 -0
  11. pineuro-1.0.0/src/pineuro/display.py +130 -0
  12. pineuro-1.0.0/src/pineuro/feedback.py +4177 -0
  13. pineuro-1.0.0/src/pineuro/gui/__init__.py +94 -0
  14. pineuro-1.0.0/src/pineuro/gui/app.py +702 -0
  15. pineuro-1.0.0/src/pineuro/gui/components/__init__.py +142 -0
  16. pineuro-1.0.0/src/pineuro/gui/components/activation_plot.py +377 -0
  17. pineuro-1.0.0/src/pineuro/gui/components/base_dialog.py +60 -0
  18. pineuro-1.0.0/src/pineuro/gui/components/collection_progress.py +304 -0
  19. pineuro-1.0.0/src/pineuro/gui/components/csv_poller.py +272 -0
  20. pineuro-1.0.0/src/pineuro/gui/components/disk_space_bar.py +154 -0
  21. pineuro-1.0.0/src/pineuro/gui/components/feedback_summary.py +486 -0
  22. pineuro-1.0.0/src/pineuro/gui/components/manual_masks.py +98 -0
  23. pineuro-1.0.0/src/pineuro/gui/components/mask_selection_dialog.py +1759 -0
  24. pineuro-1.0.0/src/pineuro/gui/components/pipeline_runner.py +2817 -0
  25. pineuro-1.0.0/src/pineuro/gui/components/qc_plot_viewer.py +224 -0
  26. pineuro-1.0.0/src/pineuro/gui/components/qc_plots.py +492 -0
  27. pineuro-1.0.0/src/pineuro/gui/components/qc_viewer.py +346 -0
  28. pineuro-1.0.0/src/pineuro/gui/components/scanner_connection_dialog.py +225 -0
  29. pineuro-1.0.0/src/pineuro/gui/components/session_selection_dialog.py +345 -0
  30. pineuro-1.0.0/src/pineuro/gui/components/task_mirror.py +113 -0
  31. pineuro-1.0.0/src/pineuro/gui/components/unified_attempt_dialog.py +337 -0
  32. pineuro-1.0.0/src/pineuro/gui/config_manager.py +500 -0
  33. pineuro-1.0.0/src/pineuro/gui/config_snapshot.py +206 -0
  34. pineuro-1.0.0/src/pineuro/gui/dispatch.py +103 -0
  35. pineuro-1.0.0/src/pineuro/gui/models.py +272 -0
  36. pineuro-1.0.0/src/pineuro/gui/screens/__init__.py +6 -0
  37. pineuro-1.0.0/src/pineuro/gui/screens/_feedback_tab.py +1198 -0
  38. pineuro-1.0.0/src/pineuro/gui/screens/_localizer_tab.py +1081 -0
  39. pineuro-1.0.0/src/pineuro/gui/screens/participant_workflow.py +8253 -0
  40. pineuro-1.0.0/src/pineuro/gui/screens/study_editor.py +2200 -0
  41. pineuro-1.0.0/src/pineuro/gui/styles.py +406 -0
  42. pineuro-1.0.0/src/pineuro/gui/utils.py +1092 -0
  43. pineuro-1.0.0/src/pineuro/mask_extraction.py +4399 -0
  44. pineuro-1.0.0/src/pineuro/preprocessing.py +1605 -0
  45. pineuro-1.0.0/src/pineuro/py.typed +0 -0
  46. pineuro-1.0.0/src/pineuro/qc.py +4082 -0
  47. pineuro-1.0.0/src/pineuro/scanner_setup.py +466 -0
  48. pineuro-1.0.0/src/pineuro/script_generator.py +401 -0
  49. pineuro-1.0.0/src/pineuro/session.py +2991 -0
  50. pineuro-1.0.0/src/pineuro/sham.py +447 -0
  51. pineuro-1.0.0/src/pineuro/simulator.py +746 -0
  52. pineuro-1.0.0/src/pineuro/storage.py +123 -0
  53. pineuro-1.0.0/src/pineuro/tasks/__init__.py +276 -0
  54. pineuro-1.0.0/src/pineuro/tasks/ball.py +1334 -0
  55. pineuro-1.0.0/src/pineuro/tasks/base.py +661 -0
  56. pineuro-1.0.0/src/pineuro/tasks/designs.py +233 -0
  57. pineuro-1.0.0/src/pineuro/tasks/factory.py +222 -0
  58. pineuro-1.0.0/src/pineuro/tasks/gauge.py +858 -0
  59. pineuro-1.0.0/src/pineuro/tasks/localizer_base.py +255 -0
  60. pineuro-1.0.0/src/pineuro/tasks/protocol.py +102 -0
  61. pineuro-1.0.0/src/pineuro/tasks/rest.py +129 -0
  62. pineuro-1.0.0/src/pineuro/tasks/rit.py +1158 -0
  63. pineuro-1.0.0/src/pineuro/tasks/rit_experience_sampling.py +1408 -0
  64. pineuro-1.0.0/src/pineuro/tasks/rit_triggered_ball.py +1507 -0
  65. pineuro-1.0.0/src/pineuro/tasks/self_other.py +525 -0
  66. pineuro-1.0.0/src/pineuro/tasks/state.py +492 -0
  67. pineuro-1.0.0/src/pineuro/tasks/subprocess_runner.py +438 -0
  68. pineuro-1.0.0/src/pineuro/tasks/task_base.py +1495 -0
  69. pineuro-1.0.0/src/pineuro/tasks/task_registry.py +611 -0
  70. pineuro-1.0.0/src/pineuro/tasks/thermometer.py +1371 -0
  71. pineuro-1.0.0/src/pineuro/tasks/ui_helpers.py +364 -0
  72. pineuro-1.0.0/src/pineuro/tasks/utils.py +222 -0
  73. pineuro-1.0.0/src/pineuro/utils.py +1971 -0
  74. pineuro-1.0.0/src/pineuro.egg-info/PKG-INFO +121 -0
  75. pineuro-1.0.0/src/pineuro.egg-info/SOURCES.txt +97 -0
  76. pineuro-1.0.0/src/pineuro.egg-info/dependency_links.txt +1 -0
  77. pineuro-1.0.0/src/pineuro.egg-info/entry_points.txt +2 -0
  78. pineuro-1.0.0/src/pineuro.egg-info/requires.txt +29 -0
  79. pineuro-1.0.0/src/pineuro.egg-info/top_level.txt +1 -0
  80. pineuro-1.0.0/tests/test_atlas_registry.py +70 -0
  81. pineuro-1.0.0/tests/test_backup.py +147 -0
  82. pineuro-1.0.0/tests/test_collection_qc.py +212 -0
  83. pineuro-1.0.0/tests/test_config.py +53 -0
  84. pineuro-1.0.0/tests/test_discard_attempt.py +226 -0
  85. pineuro-1.0.0/tests/test_display.py +84 -0
  86. pineuro-1.0.0/tests/test_feedback.py +712 -0
  87. pineuro-1.0.0/tests/test_mask_extraction.py +585 -0
  88. pineuro-1.0.0/tests/test_preprocessing.py +471 -0
  89. pineuro-1.0.0/tests/test_qc.py +258 -0
  90. pineuro-1.0.0/tests/test_scanner_setup.py +360 -0
  91. pineuro-1.0.0/tests/test_script_generator.py +263 -0
  92. pineuro-1.0.0/tests/test_session_config.py +641 -0
  93. pineuro-1.0.0/tests/test_session_orchestration.py +374 -0
  94. pineuro-1.0.0/tests/test_sham.py +199 -0
  95. pineuro-1.0.0/tests/test_simulator.py +345 -0
  96. pineuro-1.0.0/tests/test_smoke.py +7 -0
  97. pineuro-1.0.0/tests/test_storage.py +83 -0
  98. pineuro-1.0.0/tests/test_subprocess_runner.py +319 -0
  99. pineuro-1.0.0/tests/test_utils.py +740 -0
pineuro-1.0.0/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2026, Marlee M Vandewouw
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
pineuro-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: pineuro
3
+ Version: 1.0.0
4
+ Summary: Python for Individualized Neurofeedback
5
+ Author-email: Marlee M Vandewouw <marlee.vandewouw@outlook.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro
8
+ Project-URL: Documentation, https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro/tree/main/docs
9
+ Project-URL: Issues, https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro/issues
10
+ Project-URL: Changelog, https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro/blob/main/CHANGELOG.md
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: Microsoft :: Windows
19
+ Requires-Python: <3.12,>=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: antspyx==0.6.2
23
+ Requires-Dist: dcm2niix
24
+ Requires-Dist: evdev; sys_platform == "linux"
25
+ Requires-Dist: flet==0.80.5
26
+ Requires-Dist: flet-charts>=0.1.0
27
+ Requires-Dist: matplotlib
28
+ Requires-Dist: nibabel>=5.3.0
29
+ Requires-Dist: nilearn==0.13.1
30
+ Requires-Dist: numpy>=2.0.0
31
+ Requires-Dist: pandas>=2.0.0
32
+ Requires-Dist: psutil>=5.9
33
+ Requires-Dist: psychopy>=2025.2.0
34
+ Requires-Dist: pygame>=2.6.0
35
+ Requires-Dist: pydicom
36
+ Requires-Dist: python-xlib; sys_platform == "linux"
37
+ Requires-Dist: pyyaml
38
+ Requires-Dist: scipy>=1.14.0
39
+ Requires-Dist: screeninfo>=0.8
40
+ Requires-Dist: templateflow
41
+ Requires-Dist: watchdog
42
+ Provides-Extra: dev
43
+ Requires-Dist: build; extra == "dev"
44
+ Requires-Dist: mypy==1.19.1; extra == "dev"
45
+ Requires-Dist: pytest>=8.0; extra == "dev"
46
+ Requires-Dist: ruff==0.15.2; extra == "dev"
47
+ Requires-Dist: types-PyYAML; extra == "dev"
48
+ Dynamic: license-file
49
+
50
+ # pineuro
51
+
52
+ **Python for Individualized Neurofeedback**
53
+
54
+ A Python library for real-time fMRI neurofeedback research. pineuro provides a complete pipeline from DICOM acquisition through preprocessing, network mask extraction, and moment-to-moment BOLD activation estimation, with a desktop GUI for study management and participant workflows.
55
+
56
+ ## Features
57
+
58
+ **Core pipeline**
59
+ - **DICOM processing**: Watch for incoming scanner DICOMs and process volumes as they arrive
60
+ - **Preprocessing**: Motion correction, brain extraction, and multi-run coregistration (ANTsPy)
61
+ - **ICA mask extraction**: Participant-specific network masks via ICA and Yeo atlas
62
+ - **GLM mask extraction**: Network masks from task localizer contrasts
63
+ - **MNI atlas masks**: Atlas-based ROIs without a localizer run
64
+ - **Incremental GLM**: Online activation estimation using Gentleman's algorithm
65
+ - **Connectivity feedback**: Real-time functional connectivity between network pairs
66
+
67
+ **Study management**
68
+ - **Desktop GUI**: Study configuration and participant workflows (`pineuro-gui`)
69
+ - **Session orchestration**: Multi-run and multi-session protocols
70
+ - **Sham / control conditions**: Sham feedback, replay providers, and randomization
71
+ - **QC reports**: HTML quality reports for each pipeline stage
72
+
73
+ **Task framework**
74
+ - **Built-in tasks**: Gauge (dual-network block design), ball (dual-network adaptive), thermometer (single-network), and RIT variants
75
+ - **Task plugin system**: `@pineuro_task` decorator and entry-point registration
76
+ - **Simulation mode**: Offline DICOM replay at configurable speed
77
+
78
+ ## Installation
79
+
80
+ **Requires Python 3.10 or 3.11** (`>=3.10,<3.12`). All dependencies are pure pip; no external tools required.
81
+
82
+ ```bash
83
+ git clone https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro.git
84
+ cd pineuro
85
+ pip install -e .
86
+ ```
87
+
88
+ See the [Installation Guide](docs/getting-started/installation.md) for platform-specific notes and troubleshooting.
89
+
90
+ ## GUI
91
+
92
+ ```bash
93
+ pineuro-gui
94
+ ```
95
+
96
+ The desktop GUI provides study configuration, participant workflows, live activation plots, and QC reports. See the [First Session Tutorial](docs/getting-started/first-session.md) for a complete walkthrough.
97
+
98
+ ## Documentation
99
+
100
+ **Getting started**
101
+ - [Installation](docs/getting-started/installation.md): Setup and dependencies
102
+ - [Quick Start](docs/getting-started/quickstart.md): GUI quick start (with an API snippet)
103
+ - [First Session](docs/getting-started/first-session.md): End-to-end GUI walkthrough
104
+
105
+ **User guide**: [Full index](docs/index.md)
106
+
107
+ [Pipeline Overview](docs/user-guide/pipeline-overview.md) · [Study Configuration](docs/user-guide/study-configuration.md) · [Preprocessing](docs/user-guide/preprocessing.md) · [Mask Extraction](docs/user-guide/mask-extraction.md) · [Feedback Tasks](docs/user-guide/feedback-tasks.md) · [Tasks](docs/user-guide/tasks.md) · [Sham & Blinding](docs/user-guide/sham-and-blinding.md) · [Multi-Session](docs/user-guide/multi-session.md) · [Display Setup](docs/user-guide/display-setup.md) · [Simulation Mode](docs/user-guide/simulation-mode.md) · [QC Reports](docs/user-guide/qc-reports.md) · [Network Testing](docs/user-guide/network-testing.md)
108
+
109
+ ## Citation
110
+
111
+ If you use pineuro in your research, please cite:
112
+
113
+ ```
114
+ Vandewouw, M. M. (2026). pineuro: Python for Individualized Neurofeedback.
115
+ https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro
116
+ ```
117
+
118
+ ## License
119
+
120
+ Apache License 2.0. See [LICENSE](LICENSE) for details.
121
+
@@ -0,0 +1,72 @@
1
+ # pineuro
2
+
3
+ **Python for Individualized Neurofeedback**
4
+
5
+ A Python library for real-time fMRI neurofeedback research. pineuro provides a complete pipeline from DICOM acquisition through preprocessing, network mask extraction, and moment-to-moment BOLD activation estimation, with a desktop GUI for study management and participant workflows.
6
+
7
+ ## Features
8
+
9
+ **Core pipeline**
10
+ - **DICOM processing**: Watch for incoming scanner DICOMs and process volumes as they arrive
11
+ - **Preprocessing**: Motion correction, brain extraction, and multi-run coregistration (ANTsPy)
12
+ - **ICA mask extraction**: Participant-specific network masks via ICA and Yeo atlas
13
+ - **GLM mask extraction**: Network masks from task localizer contrasts
14
+ - **MNI atlas masks**: Atlas-based ROIs without a localizer run
15
+ - **Incremental GLM**: Online activation estimation using Gentleman's algorithm
16
+ - **Connectivity feedback**: Real-time functional connectivity between network pairs
17
+
18
+ **Study management**
19
+ - **Desktop GUI**: Study configuration and participant workflows (`pineuro-gui`)
20
+ - **Session orchestration**: Multi-run and multi-session protocols
21
+ - **Sham / control conditions**: Sham feedback, replay providers, and randomization
22
+ - **QC reports**: HTML quality reports for each pipeline stage
23
+
24
+ **Task framework**
25
+ - **Built-in tasks**: Gauge (dual-network block design), ball (dual-network adaptive), thermometer (single-network), and RIT variants
26
+ - **Task plugin system**: `@pineuro_task` decorator and entry-point registration
27
+ - **Simulation mode**: Offline DICOM replay at configurable speed
28
+
29
+ ## Installation
30
+
31
+ **Requires Python 3.10 or 3.11** (`>=3.10,<3.12`). All dependencies are pure pip; no external tools required.
32
+
33
+ ```bash
34
+ git clone https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro.git
35
+ cd pineuro
36
+ pip install -e .
37
+ ```
38
+
39
+ See the [Installation Guide](docs/getting-started/installation.md) for platform-specific notes and troubleshooting.
40
+
41
+ ## GUI
42
+
43
+ ```bash
44
+ pineuro-gui
45
+ ```
46
+
47
+ The desktop GUI provides study configuration, participant workflows, live activation plots, and QC reports. See the [First Session Tutorial](docs/getting-started/first-session.md) for a complete walkthrough.
48
+
49
+ ## Documentation
50
+
51
+ **Getting started**
52
+ - [Installation](docs/getting-started/installation.md): Setup and dependencies
53
+ - [Quick Start](docs/getting-started/quickstart.md): GUI quick start (with an API snippet)
54
+ - [First Session](docs/getting-started/first-session.md): End-to-end GUI walkthrough
55
+
56
+ **User guide**: [Full index](docs/index.md)
57
+
58
+ [Pipeline Overview](docs/user-guide/pipeline-overview.md) · [Study Configuration](docs/user-guide/study-configuration.md) · [Preprocessing](docs/user-guide/preprocessing.md) · [Mask Extraction](docs/user-guide/mask-extraction.md) · [Feedback Tasks](docs/user-guide/feedback-tasks.md) · [Tasks](docs/user-guide/tasks.md) · [Sham & Blinding](docs/user-guide/sham-and-blinding.md) · [Multi-Session](docs/user-guide/multi-session.md) · [Display Setup](docs/user-guide/display-setup.md) · [Simulation Mode](docs/user-guide/simulation-mode.md) · [QC Reports](docs/user-guide/qc-reports.md) · [Network Testing](docs/user-guide/network-testing.md)
59
+
60
+ ## Citation
61
+
62
+ If you use pineuro in your research, please cite:
63
+
64
+ ```
65
+ Vandewouw, M. M. (2026). pineuro: Python for Individualized Neurofeedback.
66
+ https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro
67
+ ```
68
+
69
+ ## License
70
+
71
+ Apache License 2.0. See [LICENSE](LICENSE) for details.
72
+
@@ -0,0 +1,145 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pineuro"
7
+ version = "1.0.0"
8
+ description = "Python for Individualized Neurofeedback"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10,<3.12"
11
+ authors = [{name = "Marlee M Vandewouw", email = "marlee.vandewouw@outlook.com"}]
12
+ license = "Apache-2.0"
13
+ classifiers = [
14
+ "Development Status :: 5 - Production/Stable",
15
+ "Intended Audience :: Science/Research",
16
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Operating System :: MacOS",
21
+ "Operating System :: Microsoft :: Windows",
22
+ ]
23
+ dependencies = [
24
+ # antspyx/nilearn are exact-pinned: the validation suite asserts their
25
+ # output via exact / first-principles checks that only reproduce when the
26
+ # iterative optimizers run deterministically (single-threaded ITK/BLAS; see
27
+ # tests/validation/conftest.py). A version bump can change that output, so it
28
+ # must be a deliberate, re-verified change -- see tests/validation/README.md.
29
+ "antspyx==0.6.2",
30
+ "dcm2niix",
31
+ # Linux-only: reads /dev/input/event* for global key polling under Wayland,
32
+ # where XQueryKeymap is starved of events when the stimulus window is
33
+ # unfocused (see tasks/task_base.py:_poll_keys_evdev). Compiles against
34
+ # linux/input.h, so it must not install on Windows/macOS.
35
+ "evdev; sys_platform == 'linux'",
36
+ "flet==0.80.5",
37
+ "flet-charts>=0.1.0",
38
+ "matplotlib",
39
+ "nibabel>=5.3.0",
40
+ "nilearn==0.13.1", # exact-pinned -- see the antspyx note above
41
+ "numpy>=2.0.0",
42
+ "pandas>=2.0.0",
43
+ "psutil>=5.9", # Terminate the ICA subprocess tree on GUI Stop (mask_extraction)
44
+ "psychopy>=2025.2.0",
45
+ "pygame>=2.6.0", # Cross-platform audio backend for PsychoPy
46
+ "pydicom",
47
+ # Linux-only: Xlib window positioning (_move_window_xlib) and the Xorg
48
+ # unfocused-key poll path (XQueryKeymap). Present transitively via PsychoPy
49
+ # today, but pineuro uses it directly, so depend on it explicitly.
50
+ "python-xlib; sys_platform == 'linux'",
51
+ "pyyaml",
52
+ "scipy>=1.14.0",
53
+ "screeninfo>=0.8",
54
+ "templateflow",
55
+ "watchdog",
56
+ ]
57
+
58
+ [project.urls]
59
+ Homepage = "https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro"
60
+ Documentation = "https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro/tree/main/docs"
61
+ Issues = "https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro/issues"
62
+ Changelog = "https://github.com/WhitfieldGabrieli-EPICBrainLab/pineuro/blob/main/CHANGELOG.md"
63
+
64
+ [project.optional-dependencies]
65
+ # ruff and mypy are exact-pinned so the checks CI runs match a local
66
+ # `pip install -e ".[dev]"`: a new release can change their output on unchanged
67
+ # code (ruff 0.17 formats code blocks in Markdown files; 0.15 did not). Upgrade them
68
+ # deliberately, together with the ruff install in .github/workflows/ci.yml.
69
+ dev = ["build", "mypy==1.19.1", "pytest>=8.0", "ruff==0.15.2", "types-PyYAML"]
70
+
71
+ [project.scripts]
72
+ pineuro-gui = "pineuro.gui.app:main"
73
+
74
+ # Third-party task plugins register under this entry-point group.
75
+ # In a plugin package's pyproject.toml:
76
+ # [project.entry-points."pineuro.tasks"]
77
+ # my_task = "my_package.tasks"
78
+ # pineuro auto-discovers all registered plugins at GUI startup.
79
+ [project.entry-points."pineuro.tasks"]
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Ruff — linter + formatter
83
+ # ---------------------------------------------------------------------------
84
+
85
+ [tool.ruff]
86
+ line-length = 120
87
+ target-version = "py310"
88
+ exclude = [".git", ".venv", "__pycache__", "*.egg-info", "build", "dist"]
89
+
90
+ [tool.ruff.lint]
91
+ select = ["E", "W", "F", "I", "UP", "B", "SIM"]
92
+ ignore = [
93
+ "E501", # line too long — formatter handles this
94
+ "E402", # module-level import not at top — lazy PsychoPy imports are intentional
95
+ "UP007", # Use X | Y for Union — noisy to enforce retroactively
96
+ "B007", # loop variable not used — common in NumPy loops (for _ in range(n))
97
+ "B008", # function call in default arg — legitimate for np.array() defaults
98
+ "SIM108", # use ternary — hurts readability in numerical code
99
+ ]
100
+ fixable = ["I", "UP", "E", "W", "F401"]
101
+
102
+ [tool.ruff.lint.per-file-ignores]
103
+ "__init__.py" = ["F401"]
104
+ "tests/*.py" = ["T20", "S101", "INP001", "E402"]
105
+ "tests/**/*.py" = ["T20", "S101", "INP001", "E402"]
106
+ "analysis/*.py" = ["T20", "S101", "INP001", "E402"]
107
+ "src/pineuro/gui/**/*.py" = ["B006"]
108
+ "src/pineuro/tasks/*.py" = ["E402"]
109
+
110
+ [tool.ruff.lint.isort]
111
+ known-first-party = ["pineuro"]
112
+ force-sort-within-sections = false
113
+ split-on-trailing-comma = true
114
+
115
+ [tool.ruff.format]
116
+ quote-style = "double"
117
+ indent-style = "space"
118
+ skip-magic-trailing-comma = false
119
+ docstring-code-format = false
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # pytest
123
+ # ---------------------------------------------------------------------------
124
+
125
+ [tool.pytest.ini_options]
126
+ testpaths = ["tests"]
127
+ markers = [
128
+ "slow: marks tests as slow (>10s)",
129
+ "validation: scientific-correctness validation (real ANTs/nilearn/dcm2niix, synthetic ground truth)",
130
+ ]
131
+ addopts = "-m 'not validation'"
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # mypy — basic type checking
135
+ # ---------------------------------------------------------------------------
136
+
137
+ [tool.mypy]
138
+ # Keep in step with the Python of the lint-and-typecheck job in .github/workflows/ci.yml.
139
+ python_version = "3.10"
140
+ warn_return_any = true
141
+ warn_unused_configs = true
142
+ ignore_missing_imports = true
143
+ no_implicit_optional = false
144
+ disable_error_code = ["import-untyped"]
145
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,284 @@
1
+ """pineuro - Python Real-Time fMRI Neurofeedback
2
+
3
+ A complete pipeline for real-time fMRI neurofeedback research, from volume acquisition
4
+ through preprocessing, network mask extraction, and moment-to-moment BOLD activation estimation.
5
+
6
+ Main Functions
7
+ --------------
8
+ run_feedback_task : Run a complete real-time feedback task
9
+ preprocessing : DICOM conversion, motion correction, brain extraction
10
+ collect_volumes : Watch for and collect incoming scanner volumes (DICOM or NIfTI)
11
+ mask_extraction_rest : Extract network masks using ICA and Yeo atlas
12
+ mask_extraction_mni : Extract network masks directly from MNI atlas (no localizer needed)
13
+ mask_extraction_task : Extract network masks using GLM contrasts from task localizer
14
+ coregister_reference : Coregister masks to real-time scanner position
15
+
16
+ Task Framework
17
+ --------------
18
+ BaseTask : Abstract base for custom neurofeedback tasks
19
+ BaseLocalizerTask : Abstract base for localizer/stimulus tasks
20
+ BallTask : Built-in dual-network ball game with adaptive difficulty
21
+ ThermometerTask : Built-in single-network thermometer feedback
22
+ create_task : Factory function to create tasks from registry
23
+ NetworkRole : Declare network slot semantics for tasks
24
+ ActivationSmoother : Exponential moving average smoothing
25
+ ActivationNormalizer : Percentile-based normalization
26
+
27
+ Session Orchestration
28
+ --------------------
29
+ SessionConfig : High-level session configuration
30
+ LocalizerConfig / FeedbackRunConfig : Per-localizer and per-feedback-run configuration
31
+ run_full_session : Run complete multi-run session
32
+ setup_session : Initialize session (collection, preprocessing, mask extraction)
33
+ run_single_task : Run one feedback/transfer task via task factory
34
+ TaskState : Persistent state across runs
35
+
36
+ Examples
37
+ --------
38
+ >>> from pineuro import preprocessing, mask_extraction_rest, run_feedback_task
39
+ >>>
40
+ >>> # Step 1: Preprocess resting-state data
41
+ >>> preproc_file, mask_file, ref_file = preprocessing(
42
+ ... participant_id='sub-01', session_id='ses-01', task='rest',
43
+ ... root_dir='/data/sub-01/ses-01/preprocessing',
44
+ ... input_dicoms='/scanner/sub-01/rest',
45
+ ... )
46
+ >>>
47
+ >>> # Step 2: Extract participant-specific network masks (ICA + Yeo atlas)
48
+ >>> mask_paths = mask_extraction_rest(
49
+ ... participant_id='sub-01', session_id='ses-01',
50
+ ... root_dir='/data/sub-01/ses-01/mask_extraction',
51
+ ... input_file=preproc_file, mask_file=mask_file, ref_file=ref_file,
52
+ ... networks=['ContA', 'DefaultA'], # returns a list of mask Paths
53
+ ... )
54
+ >>>
55
+ >>> # Step 3: Run a real-time feedback task (here, simulated offline)
56
+ >>> result = run_feedback_task(
57
+ ... mask_paths=mask_paths, subject='sub-01', session='ses-01',
58
+ ... task='ball', run=1,
59
+ ... output_dir='/data/sub-01/ses-01/feedback',
60
+ ... collection_dir='/scanner/incoming',
61
+ ... n_baseline=15, n_volumes=200, tr=2.0,
62
+ ... simulator_source_dir='/data/sub-01/feedback_dicoms', simulator_speed=10.0,
63
+ ... )
64
+ """
65
+
66
+ import importlib as _importlib
67
+ from importlib.metadata import PackageNotFoundError as _PackageNotFoundError
68
+ from importlib.metadata import version as _pkg_version
69
+
70
+ try:
71
+ # Single source of truth: the version declared in pyproject.toml and
72
+ # recorded in the installed package metadata.
73
+ __version__ = _pkg_version("pineuro")
74
+ except _PackageNotFoundError: # pragma: no cover - package not installed
75
+ __version__ = "unknown"
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Lazy import system (PEP 562)
79
+ #
80
+ # Heavy neuroimaging libraries (ANTsPy, nilearn, scipy, nibabel) take 8-17s
81
+ # to import on cold start. By deferring submodule imports until first access,
82
+ # the GUI starts instantly and scripts only pay for what they actually use.
83
+ # ---------------------------------------------------------------------------
84
+
85
+ # Mapping: attribute_name -> (submodule_path, attribute_name_in_submodule)
86
+ _LAZY_IMPORTS: dict[str, tuple[str, str]] = {
87
+ # --- feedback.py (heavy: nibabel, scipy, watchdog, pydicom) ---
88
+ "run_feedback_task": (".feedback", "run_feedback_task"),
89
+ "ConnectivityEstimator": (".feedback", "ConnectivityEstimator"),
90
+ "get_next_run_number": (".feedback", "get_next_run_number"),
91
+ "get_slot_run_info": (".feedback", "get_slot_run_info"),
92
+ # --- preprocessing.py (heavy: nibabel, scipy) ---
93
+ "preprocessing": (".preprocessing", "preprocessing"),
94
+ "collect_volumes": (".preprocessing", "collect_volumes"),
95
+ # --- mask_extraction.py (heavy: nibabel, scipy, pandas) ---
96
+ "mask_extraction_rest": (".mask_extraction", "mask_extraction_rest"),
97
+ "mask_extraction_mni": (".mask_extraction", "mask_extraction_mni"),
98
+ "mask_extraction_task": (".mask_extraction", "mask_extraction_task"),
99
+ "run_mask_extraction_from_config": (".mask_extraction", "run_mask_extraction_from_config"),
100
+ "import_manual_masks": (".mask_extraction", "import_manual_masks"),
101
+ "coregister_reference": (".mask_extraction", "coregister_reference"),
102
+ "create_tissue_masks": (".mask_extraction", "create_tissue_masks"),
103
+ "ATLAS_SOURCES": (".atlas_registry", "ATLAS_SOURCES"),
104
+ "get_atlas_labels": (".mask_extraction", "get_atlas_labels"),
105
+ # --- tasks (lightweight but deferred to avoid loading task modules) ---
106
+ "BaseTask": (".tasks", "BaseTask"),
107
+ "BaseLocalizerTask": (".tasks", "BaseLocalizerTask"),
108
+ "BallTask": (".tasks", "BallTask"),
109
+ "ThermometerTask": (".tasks", "ThermometerTask"),
110
+ "ActivationSmoother": (".tasks", "ActivationSmoother"),
111
+ "ActivationNormalizer": (".tasks", "ActivationNormalizer"),
112
+ "NetworkRole": (".tasks", "NetworkRole"),
113
+ "create_task": (".tasks", "create_task"),
114
+ "create_localizer_task": (".tasks", "create_localizer_task"),
115
+ # --- tasks.state ---
116
+ "TaskState": (".tasks.state", "TaskState"),
117
+ "RunInfo": (".tasks.state", "RunInfo"),
118
+ # --- session.py (lightweight: stdlib only) ---
119
+ "SessionConfig": (".session", "SessionConfig"),
120
+ "PathConfig": (".session", "PathConfig"),
121
+ "LocalizerConfig": (".session", "LocalizerConfig"),
122
+ "LocalizerExtractionConfig": (".session", "LocalizerExtractionConfig"),
123
+ "FeedbackRunConfig": (".session", "FeedbackRunConfig"),
124
+ "QCConfig": (".session", "QCConfig"),
125
+ "GLMConfig": (".session", "GLMConfig"),
126
+ "ConnectivityConfig": (".session", "ConnectivityConfig"),
127
+ "IntermittentConfig": (".session", "IntermittentConfig"),
128
+ "SimulatorConfig": (".session", "SimulatorConfig"),
129
+ "ScannerConfig": (".session", "ScannerConfig"),
130
+ "ReferenceConfig": (".session", "ReferenceConfig"),
131
+ "SessionMetadata": (".session", "SessionMetadata"),
132
+ "ShamConfig": (".session", "ShamConfig"),
133
+ "ShamRunConfig": (".session", "ShamRunConfig"),
134
+ "SimulatorSourceMapping": (".session", "SimulatorSourceMapping"),
135
+ "setup_session": (".session", "setup_session"),
136
+ "run_single_task": (".session", "run_single_task"),
137
+ "run_full_session": (".session", "run_full_session"),
138
+ "run_next_task": (".session", "run_next_task"),
139
+ "reset_task_state": (".session", "reset_task_state"),
140
+ "resolve_localizer_masks": (".session", "resolve_localizer_masks"),
141
+ "detect_existing_sessions": (".session", "detect_existing_sessions"),
142
+ "next_session_id": (".session", "next_session_id"),
143
+ # --- sham.py ---
144
+ "ShamActivationProvider": (".sham", "ShamActivationProvider"),
145
+ "ReplayActivationProvider": (".sham", "ReplayActivationProvider"),
146
+ "load_randomization": (".sham", "load_randomization"),
147
+ "generate_randomization": (".sham", "generate_randomization"),
148
+ "get_condition_for_participant": (".sham", "get_condition_for_participant"),
149
+ "find_feedbacklog_for_replay": (".sham", "find_feedbacklog_for_replay"),
150
+ # --- utils.py (heavy: numpy, nibabel, scipy, templateflow) ---
151
+ "enable_timing": (".utils", "enable_timing"),
152
+ "disable_timing": (".utils", "disable_timing"),
153
+ "get_timing_data": (".utils", "get_timing_data"),
154
+ "save_timing_report": (".utils", "save_timing_report"),
155
+ "Timer": (".utils", "Timer"),
156
+ "check_platform_requirements": (".utils", "check_platform_requirements"),
157
+ "outputs_exist": (".utils", "outputs_exist"),
158
+ "count_volume_files": (".utils", "count_volume_files"),
159
+ "collection_is_complete": (".utils", "collection_is_complete"),
160
+ "ProgressEmitter": (".utils", "ProgressEmitter"),
161
+ "compute_hrf": (".utils", "compute_hrf"),
162
+ "compute_boxcar_task_regressor": (".utils", "compute_boxcar_task_regressor"),
163
+ # --- script_generator.py ---
164
+ "generate_mask_extraction_script": (".script_generator", "generate_mask_extraction_script"),
165
+ "save_script": (".script_generator", "save_script"),
166
+ # --- scanner_setup.py (lightweight: stdlib only) ---
167
+ "test_scanner_connection": (".scanner_setup", "test_scanner_connection"),
168
+ "ConnectionTestResult": (".scanner_setup", "ConnectionTestResult"),
169
+ "ping_scanner": (".scanner_setup", "ping_scanner"),
170
+ "check_watch_directory": (".scanner_setup", "check_watch_directory"),
171
+ "get_setup_instructions": (".scanner_setup", "get_setup_instructions"),
172
+ # --- config module ---
173
+ "config": (".config", ""), # Module import, not attribute
174
+ }
175
+
176
+
177
+ def __getattr__(name: str):
178
+ if name in _LAZY_IMPORTS:
179
+ module_path, attr_name = _LAZY_IMPORTS[name]
180
+ module = _importlib.import_module(module_path, __name__)
181
+ if not attr_name:
182
+ # Module-level import (e.g., config)
183
+ val = module
184
+ else:
185
+ val = getattr(module, attr_name)
186
+ globals()[name] = val # Cache for subsequent access
187
+ return val
188
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
189
+
190
+
191
+ __all__ = [
192
+ # Version
193
+ "__version__",
194
+ # Core functions
195
+ "run_feedback_task",
196
+ "ConnectivityEstimator",
197
+ "get_next_run_number",
198
+ "get_slot_run_info",
199
+ "preprocessing",
200
+ "collect_volumes",
201
+ "mask_extraction_rest",
202
+ "mask_extraction_mni",
203
+ "mask_extraction_task",
204
+ "run_mask_extraction_from_config",
205
+ "import_manual_masks",
206
+ "coregister_reference",
207
+ "ATLAS_SOURCES",
208
+ "get_atlas_labels",
209
+ # Task classes
210
+ "BaseTask",
211
+ "BaseLocalizerTask",
212
+ "BallTask",
213
+ "ThermometerTask",
214
+ "ActivationSmoother",
215
+ "ActivationNormalizer",
216
+ # Task registry & factory
217
+ "NetworkRole",
218
+ "create_task",
219
+ "create_localizer_task",
220
+ # Task regressor computation
221
+ "compute_hrf",
222
+ "compute_boxcar_task_regressor",
223
+ # Task state
224
+ "TaskState",
225
+ "RunInfo",
226
+ # Configuration dataclasses
227
+ "SessionConfig",
228
+ "PathConfig",
229
+ "LocalizerConfig",
230
+ "LocalizerExtractionConfig",
231
+ "FeedbackRunConfig",
232
+ "QCConfig",
233
+ "GLMConfig",
234
+ "ConnectivityConfig",
235
+ "IntermittentConfig",
236
+ "SimulatorConfig",
237
+ "ScannerConfig",
238
+ "ReferenceConfig",
239
+ "SessionMetadata",
240
+ "ShamConfig",
241
+ "ShamRunConfig",
242
+ "SimulatorSourceMapping",
243
+ # Sham functionality
244
+ "ShamActivationProvider",
245
+ "ReplayActivationProvider",
246
+ "load_randomization",
247
+ "generate_randomization",
248
+ "get_condition_for_participant",
249
+ "find_feedbacklog_for_replay",
250
+ # Orchestration functions
251
+ "setup_session",
252
+ "run_single_task",
253
+ "run_full_session",
254
+ "run_next_task",
255
+ "reset_task_state",
256
+ # Multi-session support functions
257
+ "resolve_localizer_masks",
258
+ "detect_existing_sessions",
259
+ "next_session_id",
260
+ # Timing functions
261
+ "enable_timing",
262
+ "disable_timing",
263
+ "get_timing_data",
264
+ "save_timing_report",
265
+ "Timer",
266
+ # Platform checks
267
+ "check_platform_requirements",
268
+ # Utilities
269
+ "outputs_exist",
270
+ "count_volume_files",
271
+ "collection_is_complete",
272
+ "ProgressEmitter",
273
+ # Script generation
274
+ "generate_mask_extraction_script",
275
+ "save_script",
276
+ # Scanner setup
277
+ "test_scanner_connection",
278
+ "ConnectionTestResult",
279
+ "ping_scanner",
280
+ "check_watch_directory",
281
+ "get_setup_instructions",
282
+ # Configuration
283
+ "config",
284
+ ]