infant-brain-viewer 0.1.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 (26) hide show
  1. infant_brain_viewer-0.1.0/PKG-INFO +171 -0
  2. infant_brain_viewer-0.1.0/README.md +142 -0
  3. infant_brain_viewer-0.1.0/pyproject.toml +68 -0
  4. infant_brain_viewer-0.1.0/setup.cfg +4 -0
  5. infant_brain_viewer-0.1.0/setup.py +4 -0
  6. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/__init__.py +7 -0
  7. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/bundle_animation.py +248 -0
  8. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_granger_cli.py +19 -0
  9. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_loreta_cli.py +546 -0
  10. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_preprocess.py +399 -0
  11. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_preprocess_cli.py +283 -0
  12. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_quality.py +550 -0
  13. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_quality_cli.py +483 -0
  14. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/edf_transfer_entropy_cli.py +19 -0
  15. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/effconn.py +224 -0
  16. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/fetch_tract_atlas.py +80 -0
  17. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/generate_cortex.py +54 -0
  18. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/infant_brain_viewer.py +1587 -0
  19. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/test_tractography.py +14 -0
  20. infant_brain_viewer-0.1.0/src/ infant_brain_viewer/validate_atlas.py +415 -0
  21. infant_brain_viewer-0.1.0/src/infant_brain_viewer.egg-info/PKG-INFO +171 -0
  22. infant_brain_viewer-0.1.0/src/infant_brain_viewer.egg-info/SOURCES.txt +24 -0
  23. infant_brain_viewer-0.1.0/src/infant_brain_viewer.egg-info/dependency_links.txt +1 -0
  24. infant_brain_viewer-0.1.0/src/infant_brain_viewer.egg-info/entry_points.txt +2 -0
  25. infant_brain_viewer-0.1.0/src/infant_brain_viewer.egg-info/requires.txt +10 -0
  26. infant_brain_viewer-0.1.0/src/infant_brain_viewer.egg-info/top_level.txt +1 -0
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: infant-brain-viewer
3
+ Version: 0.1.0
4
+ Summary: Infant brain visualization, EEG analysis and effective connectivity
5
+ Author: Alessandro Crimi
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/alecrimi/infantconnect
8
+ Project-URL: Repository, https://github.com/alecrimi/infantconnect
9
+ Project-URL: Issues, https://github.com/alecrimi/infantconnect/issues
10
+ Keywords: infant-brain,neuroimaging,EEG,brain-connectivity,neuroscience,PyVista
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
14
+ Classifier: Topic :: Scientific/Engineering :: Visualization
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: numpy
20
+ Requires-Dist: scipy
21
+ Requires-Dist: mne
22
+ Requires-Dist: pyvista
23
+ Requires-Dist: pyvistaqt
24
+ Requires-Dist: vtk
25
+ Requires-Dist: PySide6
26
+ Requires-Dist: pooch
27
+ Requires-Dist: nibabel
28
+ Requires-Dist: edfio
29
+
30
+ # Infant Brain 3D Visualizer
31
+
32
+ A Python desktop application for visualizing infant brain surfaces, scalp and EEG electrode positions, and brain tractography in a shared 3D view. The viewer supports age-specific infant templates and can optionally compute and display effective connectivity from EEG recordings.
33
+
34
+ ## Features
35
+
36
+ - Interactive 3D visualization of cortical surfaces and tractography.
37
+ - Infant scalp and skull geometry with EEG electrode locations.
38
+ - Age/template selection for infant head geometry (from birth to 2 years in the supplied templates; the viewer's age control extends to 72 months).
39
+ - Overlay of electrodes and tractography for spatial inspection.
40
+ - Optional effective-connectivity analysis using **Granger causality** or **transfer entropy** from EEG data.
41
+ - Loading of local atlas and surface files from the repository.
42
+
43
+ ## Screenshots
44
+
45
+ ### EEG electrode locations
46
+
47
+ The scalp and EEG electrode positions are shown in 3D.
48
+
49
+ ![EEG electrodes on the infant scalp](EEG_electrodes.png)
50
+
51
+ ### Brain tractography and electrode overlay
52
+
53
+ Example screenshot showing the brain tractography together with the EEG electrode overlay.
54
+
55
+ ![Brain tractography overlaid with EEG electrodes](tracts.png)
56
+
57
+ > Keep `EEG_electrodes.png` and `tracts.png` in the repository root (alongside this README) so GitHub can display the images.
58
+
59
+ ## Repository files
60
+
61
+ - `infant_connect.py` — main interactive 3D viewer and application entry point.
62
+ - `generate_cortex.py` — prepares/generates the cortical surface files used by the viewer.
63
+ - `fetch_tract_atlas.py` — fetches the tractography atlas used for the tract overlay.
64
+ - `atlas.json` — atlas manifest/configuration used by the viewer.
65
+ - `tract_atlases/` — directory for tractography atlas data.
66
+ - `effconn.py` — standalone effective-connectivity implementation (optional alternative to installing `effconnpy`).
67
+ - `requirements.txt` — Python dependencies for the viewer.
68
+ - `EEG_electrodes.png` — screenshot/figure showing EEG electrode positions.
69
+ - `tracts.png` — screenshot/figure showing the tractography and electrode overlay.
70
+
71
+ The cortical surfaces should be prepared with `generate_cortex.py`, and the tractography atlas should be retrieved with `fetch_tract_atlas.py` before launching the viewer. The scalp, skull, and electrode/montage template data are downloaded automatically by the viewer when needed and cached locally.
72
+
73
+ ## Requirements
74
+
75
+ - Python 3.10 or newer is recommended.
76
+ - A desktop environment capable of running Qt and rendering VTK/PyVista 3D scenes.
77
+ - Python packages listed in `requirements.txt`.
78
+
79
+ ## Installation
80
+
81
+ Clone the repository and enter its directory:
82
+
83
+ ```bash
84
+ git clone <YOUR-REPOSITORY-URL>
85
+ cd <YOUR-REPOSITORY-DIRECTORY>
86
+ ```
87
+
88
+ Create and activate a virtual environment (recommended):
89
+
90
+ ```bash
91
+ python -m venv .venv
92
+ source .venv/bin/activate
93
+ ```
94
+
95
+ On Windows, activate it with:
96
+
97
+ ```powershell
98
+ .venv\Scripts\Activate.ps1
99
+ ```
100
+
101
+ Install the dependencies:
102
+
103
+ ```bash
104
+ python -m pip install --upgrade pip
105
+ python -m pip install -r requirements.txt
106
+ ```
107
+
108
+ ## Prepare the data
109
+
110
+ Run the preparation scripts from the repository root:
111
+
112
+ ```bash
113
+ python generate_cortex.py
114
+ python fetch_tract_atlas.py
115
+ ```
116
+
117
+ These scripts prepare the cortical surfaces and fetch the tractography atlas, respectively. Check their console output for any download or preparation errors. Make sure the resulting files are placed where the viewer expects them, and that `atlas.json` and `tract_atlases/` are present.
118
+
119
+ The viewer downloads the required infant scalp, skull, and electrode/montage template data on demand. An internet connection is needed the first time each template is requested. Downloaded template files are cached locally for later runs.
120
+
121
+ ## Run the viewer
122
+
123
+ From the repository root, run:
124
+
125
+ ```bash
126
+ python infant_connect.py
127
+ ```
128
+
129
+ Run the command from the repository root after completing the preparation steps above.
130
+
131
+ Use the viewer controls to toggle the scalp/electrodes, cortical surfaces, and tractography layers. Available controls and displayed options may depend on the atlas and surface files that have been prepared.
132
+
133
+ ## Effective connectivity
134
+
135
+ The viewer can compute and display effective connectivity from an EEG recording using **Granger causality** or **transfer entropy**.
136
+
137
+ There are two ways to provide the implementation:
138
+
139
+ 1. **Install `effconnpy`** from [piwheels](https://www.piwheels.org/project/effconnpy/):
140
+
141
+ ```bash
142
+ python -m pip install effconnpy
143
+ ```
144
+
145
+ The installed package must expose `compute_effective_connectivity` from the `effconn` module, as expected by the viewer.
146
+
147
+ 2. **Use the standalone implementation** included in this repository as `effconn.py`. Keep `effconn.py` alongside the main viewer script so that `from effconn import compute_effective_connectivity` can resolve locally. If you choose this option, you generally do not need to install a separate package providing the same module.
148
+
149
+ To run the analysis, load an EEG recording through the viewer and enable the effective-connectivity control. The available methods are Granger causality and transfer entropy. The recording's channel names need to correspond to the electrode labels supported by the selected montage/template for the electrode positions to be fitted correctly.
150
+
151
+ ## Data and attribution
152
+
153
+ The infant scalp/skull and montage templates are downloaded from the public [infant template paper repository](https://github.com/christian-oreilly/infant_template_paper/) when required. The cortical surfaces and tractography atlas are prepared or fetched by the scripts and should retain the licensing and attribution requirements of their original sources. Check the source projects and data licences before redistribution.
154
+
155
+ ## Troubleshooting
156
+
157
+ - **Missing surface or atlas files:** run `generate_cortex.py` and `fetch_tract_atlas.py` from the repository root and check their output.
158
+ - **Missing Python module:** activate the intended environment and reinstall dependencies with `python -m pip install -r requirements.txt`.
159
+ - **Qt/VTK display problems:** run the viewer in a desktop session with working graphics support; headless environments may not support the interactive window.
160
+ - **Effective-connectivity import error:** ensure either the repository's `effconn.py` is alongside the viewer or the compatible `effconnpy` package is installed.
161
+ - **Electrodes do not align with the recording:** check the EEG channel names and the montage/template selected in the viewer.
162
+
163
+ ## Citation
164
+
165
+ If you use this software in research, please cite this repository and the original sources of any cortical surfaces, infant templates, and tractography atlas data used. Add a formal software citation (for example, a Zenodo DOI) here when one is available.
166
+
167
+ ## Licence
168
+
169
+ **The repository's software licence must be specified in a `LICENSE` file before public release.** Do not assume that third-party templates, cortical surfaces, or tractography atlas data are covered by the same licence as this code. Check each source project's terms and preserve any required attribution and usage restrictions.
170
+
171
+ If you intend to release your own code under a permissive licence such as MIT, add the corresponding `LICENSE` file and confirm that this is compatible with all code you authored and redistributed. The licence for this repository does not automatically relicense external datasets or assets.
@@ -0,0 +1,142 @@
1
+ # Infant Brain 3D Visualizer
2
+
3
+ A Python desktop application for visualizing infant brain surfaces, scalp and EEG electrode positions, and brain tractography in a shared 3D view. The viewer supports age-specific infant templates and can optionally compute and display effective connectivity from EEG recordings.
4
+
5
+ ## Features
6
+
7
+ - Interactive 3D visualization of cortical surfaces and tractography.
8
+ - Infant scalp and skull geometry with EEG electrode locations.
9
+ - Age/template selection for infant head geometry (from birth to 2 years in the supplied templates; the viewer's age control extends to 72 months).
10
+ - Overlay of electrodes and tractography for spatial inspection.
11
+ - Optional effective-connectivity analysis using **Granger causality** or **transfer entropy** from EEG data.
12
+ - Loading of local atlas and surface files from the repository.
13
+
14
+ ## Screenshots
15
+
16
+ ### EEG electrode locations
17
+
18
+ The scalp and EEG electrode positions are shown in 3D.
19
+
20
+ ![EEG electrodes on the infant scalp](EEG_electrodes.png)
21
+
22
+ ### Brain tractography and electrode overlay
23
+
24
+ Example screenshot showing the brain tractography together with the EEG electrode overlay.
25
+
26
+ ![Brain tractography overlaid with EEG electrodes](tracts.png)
27
+
28
+ > Keep `EEG_electrodes.png` and `tracts.png` in the repository root (alongside this README) so GitHub can display the images.
29
+
30
+ ## Repository files
31
+
32
+ - `infant_connect.py` — main interactive 3D viewer and application entry point.
33
+ - `generate_cortex.py` — prepares/generates the cortical surface files used by the viewer.
34
+ - `fetch_tract_atlas.py` — fetches the tractography atlas used for the tract overlay.
35
+ - `atlas.json` — atlas manifest/configuration used by the viewer.
36
+ - `tract_atlases/` — directory for tractography atlas data.
37
+ - `effconn.py` — standalone effective-connectivity implementation (optional alternative to installing `effconnpy`).
38
+ - `requirements.txt` — Python dependencies for the viewer.
39
+ - `EEG_electrodes.png` — screenshot/figure showing EEG electrode positions.
40
+ - `tracts.png` — screenshot/figure showing the tractography and electrode overlay.
41
+
42
+ The cortical surfaces should be prepared with `generate_cortex.py`, and the tractography atlas should be retrieved with `fetch_tract_atlas.py` before launching the viewer. The scalp, skull, and electrode/montage template data are downloaded automatically by the viewer when needed and cached locally.
43
+
44
+ ## Requirements
45
+
46
+ - Python 3.10 or newer is recommended.
47
+ - A desktop environment capable of running Qt and rendering VTK/PyVista 3D scenes.
48
+ - Python packages listed in `requirements.txt`.
49
+
50
+ ## Installation
51
+
52
+ Clone the repository and enter its directory:
53
+
54
+ ```bash
55
+ git clone <YOUR-REPOSITORY-URL>
56
+ cd <YOUR-REPOSITORY-DIRECTORY>
57
+ ```
58
+
59
+ Create and activate a virtual environment (recommended):
60
+
61
+ ```bash
62
+ python -m venv .venv
63
+ source .venv/bin/activate
64
+ ```
65
+
66
+ On Windows, activate it with:
67
+
68
+ ```powershell
69
+ .venv\Scripts\Activate.ps1
70
+ ```
71
+
72
+ Install the dependencies:
73
+
74
+ ```bash
75
+ python -m pip install --upgrade pip
76
+ python -m pip install -r requirements.txt
77
+ ```
78
+
79
+ ## Prepare the data
80
+
81
+ Run the preparation scripts from the repository root:
82
+
83
+ ```bash
84
+ python generate_cortex.py
85
+ python fetch_tract_atlas.py
86
+ ```
87
+
88
+ These scripts prepare the cortical surfaces and fetch the tractography atlas, respectively. Check their console output for any download or preparation errors. Make sure the resulting files are placed where the viewer expects them, and that `atlas.json` and `tract_atlases/` are present.
89
+
90
+ The viewer downloads the required infant scalp, skull, and electrode/montage template data on demand. An internet connection is needed the first time each template is requested. Downloaded template files are cached locally for later runs.
91
+
92
+ ## Run the viewer
93
+
94
+ From the repository root, run:
95
+
96
+ ```bash
97
+ python infant_connect.py
98
+ ```
99
+
100
+ Run the command from the repository root after completing the preparation steps above.
101
+
102
+ Use the viewer controls to toggle the scalp/electrodes, cortical surfaces, and tractography layers. Available controls and displayed options may depend on the atlas and surface files that have been prepared.
103
+
104
+ ## Effective connectivity
105
+
106
+ The viewer can compute and display effective connectivity from an EEG recording using **Granger causality** or **transfer entropy**.
107
+
108
+ There are two ways to provide the implementation:
109
+
110
+ 1. **Install `effconnpy`** from [piwheels](https://www.piwheels.org/project/effconnpy/):
111
+
112
+ ```bash
113
+ python -m pip install effconnpy
114
+ ```
115
+
116
+ The installed package must expose `compute_effective_connectivity` from the `effconn` module, as expected by the viewer.
117
+
118
+ 2. **Use the standalone implementation** included in this repository as `effconn.py`. Keep `effconn.py` alongside the main viewer script so that `from effconn import compute_effective_connectivity` can resolve locally. If you choose this option, you generally do not need to install a separate package providing the same module.
119
+
120
+ To run the analysis, load an EEG recording through the viewer and enable the effective-connectivity control. The available methods are Granger causality and transfer entropy. The recording's channel names need to correspond to the electrode labels supported by the selected montage/template for the electrode positions to be fitted correctly.
121
+
122
+ ## Data and attribution
123
+
124
+ The infant scalp/skull and montage templates are downloaded from the public [infant template paper repository](https://github.com/christian-oreilly/infant_template_paper/) when required. The cortical surfaces and tractography atlas are prepared or fetched by the scripts and should retain the licensing and attribution requirements of their original sources. Check the source projects and data licences before redistribution.
125
+
126
+ ## Troubleshooting
127
+
128
+ - **Missing surface or atlas files:** run `generate_cortex.py` and `fetch_tract_atlas.py` from the repository root and check their output.
129
+ - **Missing Python module:** activate the intended environment and reinstall dependencies with `python -m pip install -r requirements.txt`.
130
+ - **Qt/VTK display problems:** run the viewer in a desktop session with working graphics support; headless environments may not support the interactive window.
131
+ - **Effective-connectivity import error:** ensure either the repository's `effconn.py` is alongside the viewer or the compatible `effconnpy` package is installed.
132
+ - **Electrodes do not align with the recording:** check the EEG channel names and the montage/template selected in the viewer.
133
+
134
+ ## Citation
135
+
136
+ If you use this software in research, please cite this repository and the original sources of any cortical surfaces, infant templates, and tractography atlas data used. Add a formal software citation (for example, a Zenodo DOI) here when one is available.
137
+
138
+ ## Licence
139
+
140
+ **The repository's software licence must be specified in a `LICENSE` file before public release.** Do not assume that third-party templates, cortical surfaces, or tractography atlas data are covered by the same licence as this code. Check each source project's terms and preserve any required attribution and usage restrictions.
141
+
142
+ If you intend to release your own code under a permissive licence such as MIT, add the corresponding `LICENSE` file and confirm that this is compatible with all code you authored and redistributed. The licence for this repository does not automatically relicense external datasets or assets.
@@ -0,0 +1,68 @@
1
+
2
+ [build-system]
3
+ requires = ["setuptools>=69", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "infant-brain-viewer"
8
+ version = "0.1.0"
9
+ description = "Infant brain visualization, EEG analysis and effective connectivity"
10
+ readme = "README.md"
11
+ requires-python = ">=3.10"
12
+ license = "Apache-2.0"
13
+
14
+ authors = [
15
+ { name = "Alessandro Crimi" }
16
+ ]
17
+
18
+ keywords = [
19
+ "infant-brain",
20
+ "neuroimaging",
21
+ "EEG",
22
+ "brain-connectivity",
23
+ "neuroscience",
24
+ "PyVista"
25
+ ]
26
+
27
+ classifiers = [
28
+ "Development Status :: 3 - Alpha",
29
+ "Intended Audience :: Science/Research",
30
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
31
+ "Topic :: Scientific/Engineering :: Visualization",
32
+ "Programming Language :: Python :: 3",
33
+ "Operating System :: OS Independent"
34
+ ]
35
+
36
+ dependencies = [
37
+ "numpy",
38
+ "scipy",
39
+ "mne",
40
+ "pyvista",
41
+ "pyvistaqt",
42
+ "vtk",
43
+ "PySide6",
44
+ "pooch",
45
+ "nibabel",
46
+ "edfio"
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://github.com/alecrimi/infantconnect"
51
+ Repository = "https://github.com/alecrimi/infantconnect"
52
+ Issues = "https://github.com/alecrimi/infantconnect/issues"
53
+
54
+ [project.gui-scripts]
55
+ infant-brain-viewer = "infant_brain_viewer.infant_brain_viewer:main"
56
+
57
+ [tool.setuptools.packages.find]
58
+ where = ["src"]
59
+
60
+ [tool.setuptools.package-data]
61
+ infant_brain_viewer = [
62
+ "*.json",
63
+ "*.png",
64
+ "data/**/*",
65
+ "tract_atlases/**/*",
66
+ "mne_templates/**/*"
67
+ ]
68
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+ if __name__ == "__main__":
4
+ setup()
@@ -0,0 +1,7 @@
1
+
2
+ """InfantConnect: infant brain visualization and connectivity tools."""
3
+
4
+ __version__ = "0.1.0"
5
+ __author__ = "Alessandro Crimi"
6
+
7
+ __all__ = ["__version__", "__author__"]
@@ -0,0 +1,248 @@
1
+ """Bundle-style tract animation seeded at EEG sensors.
2
+
3
+ Usage in your main script:
4
+
5
+ from bundle_animation import BundleAnimationMixin
6
+ class InfantBrainViewer(BundleAnimationMixin, QMainWindow):
7
+ ...
8
+
9
+ The mixin overrides _setup_animation() and _animate_step(), so the old versions
10
+ (and _polyline_between / _point_on_streamline / _move_actor_to) can be deleted
11
+ or left in place; they are no longer used.
12
+ """
13
+ import numpy as np
14
+ import pyvista as pv
15
+
16
+ N_BUNDLES = 3 # bundles glowing at the same time, each at its own sensor
17
+ FIBERS_PER_BUNDLE = 40 # fibers travelling together in one bundle
18
+ CANDIDATES = 200 # nearest fibers considered around the sensor
19
+ DIRECTION_COHERENCE = 0.6 # min |cos| between a fiber and the lead fiber direction
20
+ TRAIL_SAMPLES = 24
21
+ FRAME_DT = 0.03
22
+ SPEED_FACTOR = 2 # bundle speed in median-fiber-lengths per second
23
+
24
+
25
+ class BundleAnimationMixin:
26
+
27
+ # ------------------------------------------------------------------ setup
28
+ def _animation_electrodes(self):
29
+ """Electrode positions in the same (brain-fitted) frame as the tracts."""
30
+ requested_age = self.age_slider.value()
31
+ age = self._nearest_age(requested_age)
32
+ name = self._pick_scalp_template(requested_age)
33
+ if name is None:
34
+ return {}
35
+ scalp = self.scalp_templates[name]
36
+ electrodes = self.electrode_templates[name]
37
+ parts = [m for m in (self._get_mesh(age, h, "outer") for h in ("left", "right"))
38
+ if m is not None]
39
+ if not parts:
40
+ return {}
41
+ reference = parts[0] if len(parts) == 1 else parts[0].merge(parts[1:])
42
+ _, electrodes_fit = fit_template_to_brain_ref(scalp, electrodes, reference)
43
+ return electrodes_fit
44
+
45
+ def _setup_animation(self, tract_mesh):
46
+ self.anim_timer.stop()
47
+ self.anim_pulses.clear()
48
+ self._anim_rng = np.random.default_rng()
49
+
50
+ if tract_mesh.n_cells < 1:
51
+ return
52
+
53
+ electrodes = self._animation_electrodes()
54
+ if not electrodes:
55
+ return
56
+ self._anim_electrodes = electrodes
57
+
58
+ # Per-fiber point arrays (ordered along the streamline).
59
+ fibers = []
60
+ for cid in range(tract_mesh.n_cells):
61
+ try:
62
+ pts = np.asarray(tract_mesh.get_cell(cid).points, dtype=float)
63
+ except Exception:
64
+ continue
65
+ if len(pts) >= 3:
66
+ fibers.append(pts)
67
+ if not fibers:
68
+ return
69
+ self._anim_fibers = fibers
70
+ self._anim_all_pts = np.vstack(fibers)
71
+ self._anim_cell_of_pt = np.concatenate(
72
+ [np.full(len(p), i, dtype=np.int64) for i, p in enumerate(fibers)]
73
+ )
74
+ self._anim_pt_in_cell = np.concatenate(
75
+ [np.arange(len(p), dtype=np.int64) for p in fibers]
76
+ )
77
+
78
+ used = set()
79
+ for b in range(N_BUNDLES):
80
+ bundle = self._make_bundle(b, avoid=used)
81
+ if bundle is None:
82
+ continue
83
+ used.add(bundle["electrode"])
84
+ # Stagger the bundles so they do not all fire in sync.
85
+ bundle["d"] = -bundle["cycle_len"] * (b / max(N_BUNDLES, 1)) * 0.6
86
+ self.anim_pulses.append(bundle)
87
+
88
+ if self.anim_pulses:
89
+ self.anim_timer.start(30)
90
+
91
+ def _make_bundle(self, slot, avoid=()):
92
+ """Pick a random sensor and gather the coherent fibers passing near it."""
93
+ names = [n for n in self._anim_electrodes if n not in avoid]
94
+ if not names:
95
+ names = list(self._anim_electrodes)
96
+ for _ in range(10): # retry if a sensor has no usable fibers nearby
97
+ name = names[int(self._anim_rng.integers(len(names)))]
98
+ e = np.asarray(self._anim_electrodes[name], dtype=float)
99
+
100
+ dist = np.linalg.norm(self._anim_all_pts - e, axis=1)
101
+ order = np.argsort(dist)
102
+
103
+ # Nearest point per fiber, for the CANDIDATES nearest fibers.
104
+ seen, cand = set(), []
105
+ for pi in order:
106
+ ci = int(self._anim_cell_of_pt[pi])
107
+ if ci in seen:
108
+ continue
109
+ seen.add(ci)
110
+ cand.append((ci, int(self._anim_pt_in_cell[pi])))
111
+ if len(cand) >= CANDIDATES:
112
+ break
113
+
114
+ # Local tangent at the closest point; align everything to the lead fiber.
115
+ tangents = []
116
+ for ci, k in cand:
117
+ pts = self._anim_fibers[ci]
118
+ t = pts[min(k + 1, len(pts) - 1)] - pts[max(k - 1, 0)]
119
+ n = np.linalg.norm(t)
120
+ tangents.append(t / n if n > 1e-12 else np.zeros(3))
121
+ lead = next((t for t in tangents if np.linalg.norm(t) > 0), None)
122
+ if lead is None:
123
+ continue
124
+
125
+ chosen = []
126
+ for (ci, k), t in zip(cand, tangents):
127
+ c = float(np.dot(t, lead))
128
+ if abs(c) < DIRECTION_COHERENCE:
129
+ continue
130
+ pts = self._anim_fibers[ci]
131
+ # Orient along the shared direction; start at the sensor-nearest point.
132
+ path = pts[k:] if c > 0 else pts[:k + 1][::-1]
133
+ if len(path) < 3:
134
+ continue
135
+ seg = np.linalg.norm(np.diff(path, axis=0), axis=1)
136
+ arc = np.concatenate(([0.0], np.cumsum(seg)))
137
+ if arc[-1] <= 1e-9:
138
+ continue
139
+ chosen.append({"pts": path, "arc": arc, "L": float(arc[-1])})
140
+ if len(chosen) >= FIBERS_PER_BUNDLE:
141
+ break
142
+ if len(chosen) < 5:
143
+ continue
144
+
145
+ lengths = np.array([f["L"] for f in chosen])
146
+ med = float(np.median(lengths))
147
+ trail = 0.20 * med
148
+ for f in chosen: # small per-fiber lead/lag keeps the bundle organic
149
+ f["offset"] = float(self._anim_rng.uniform(0.0, 0.04 * med))
150
+
151
+ tag = f"{slot}"
152
+ ph_line = pv.Line((0, 0, 0), (0, 0, 0.001))
153
+ ph_pts = pv.PolyData(np.zeros((1, 3)))
154
+ halo = self.plotter.add_mesh(
155
+ ph_line, color="#00BFFF", line_width=7, opacity=0.25,
156
+ lighting=False, reset_camera=False, name=f"bundle_halo_{tag}")
157
+ core = self.plotter.add_mesh(
158
+ ph_line.copy(), color="#7DEBFF", line_width=2.0, opacity=0.95,
159
+ lighting=False, reset_camera=False, name=f"bundle_core_{tag}")
160
+ head_halo = self.plotter.add_mesh(
161
+ ph_pts, color="#00CFFF", opacity=0.30, point_size=14,
162
+ render_points_as_spheres=True, lighting=False,
163
+ reset_camera=False, name=f"bundle_head_halo_{tag}")
164
+ head_core = self.plotter.add_mesh(
165
+ ph_pts.copy(), color="#FFF4CF", opacity=1.0, point_size=5,
166
+ render_points_as_spheres=True, lighting=False,
167
+ reset_camera=False, name=f"bundle_head_core_{tag}")
168
+
169
+ return {
170
+ "slot": slot, "electrode": name, "fibers": chosen,
171
+ "d": 0.0, "speed": SPEED_FACTOR * med, "trail": trail,
172
+ "cycle_len": float(lengths.max()) + trail,
173
+ "halo": halo, "core": core,
174
+ "head_halo": head_halo, "head_core": head_core,
175
+ }
176
+ return None
177
+
178
+ # ---------------------------------------------------------------- frames
179
+ def _animate_step(self):
180
+ if not self.tracts_checkbox.isChecked() or not self.animate_checkbox.isChecked():
181
+ self.anim_timer.stop()
182
+ return
183
+
184
+ for bi, bundle in enumerate(self.anim_pulses):
185
+ bundle["d"] += bundle["speed"] * FRAME_DT
186
+
187
+ # Finished: re-seed this bundle at a new random sensor.
188
+ if bundle["d"] > bundle["cycle_len"]:
189
+ others = {b["electrode"] for j, b in enumerate(self.anim_pulses) if j != bi}
190
+ new = self._make_bundle(bundle["slot"], avoid=others | {bundle["electrode"]})
191
+ if new is not None:
192
+ self.anim_pulses[bi] = bundle = new
193
+ else:
194
+ bundle["d"] = 0.0
195
+
196
+ if bundle["d"] < 0: # staggered start: not launched yet
197
+ self._set_bundle_geometry(bundle, None, None)
198
+ continue
199
+
200
+ trail = bundle["trail"]
201
+ all_pts, all_lines, heads = [], [], []
202
+ base = 0
203
+ for f in bundle["fibers"]:
204
+ head = bundle["d"] + f["offset"]
205
+ if head <= 0:
206
+ continue
207
+ head = min(head, f["L"])
208
+ tail = max(0.0, head - trail)
209
+ if f["L"] - tail < 1e-9 or (head >= f["L"] and tail >= f["L"] - 1e-9):
210
+ continue
211
+ dists = np.linspace(tail, head, TRAIL_SAMPLES)
212
+ coords = np.column_stack(
213
+ [np.interp(dists, f["arc"], f["pts"][:, a]) for a in range(3)]
214
+ )
215
+ all_pts.append(coords)
216
+ all_lines.append(np.hstack(
217
+ ([TRAIL_SAMPLES], base + np.arange(TRAIL_SAMPLES, dtype=np.int64))))
218
+ base += TRAIL_SAMPLES
219
+ if head < f["L"]:
220
+ heads.append(coords[-1])
221
+
222
+ if not all_pts:
223
+ self._set_bundle_geometry(bundle, None, None)
224
+ continue
225
+
226
+ trail_poly = pv.PolyData(np.vstack(all_pts), lines=np.concatenate(all_lines))
227
+ head_poly = pv.PolyData(np.array(heads)) if heads else None
228
+ self._set_bundle_geometry(bundle, trail_poly, head_poly)
229
+
230
+ self.plotter.render()
231
+
232
+ @staticmethod
233
+ def _set_bundle_geometry(bundle, trail_poly, head_poly):
234
+ empty_lines = pv.Line((0, 0, 0), (0, 0, 1e-6))
235
+ empty_pts = pv.PolyData(np.zeros((1, 3)))
236
+ t = trail_poly if trail_poly is not None else empty_lines
237
+ h = head_poly if head_poly is not None else empty_pts
238
+ bundle["halo"].mapper.SetInputData(t)
239
+ bundle["core"].mapper.SetInputData(t.copy())
240
+ bundle["head_halo"].mapper.SetInputData(h)
241
+ bundle["head_core"].mapper.SetInputData(h.copy())
242
+
243
+
244
+ def fit_template_to_brain_ref(scalp, electrodes, reference):
245
+ """Late import so this module can sit next to the main script without a cycle."""
246
+ import importlib
247
+ main_mod = importlib.import_module("__main__")
248
+ return main_mod.fit_template_to_brain(scalp, electrodes, reference)
@@ -0,0 +1,19 @@
1
+ """Command-line Granger-causality connectome from EDF, saved as CSV.
2
+
3
+ Uses effconn.compute_granger (pairwise F-test on OLS/VAR residuals):
4
+ * values are -log10(p); >= 1.3 means p < 0.05; 0 = no test / failed fit
5
+ * default lag = 5 samples (as in the viewer)
6
+ * CSV layout follows effconn.py: row i, column j = influence of channel j
7
+ (source) on channel i (target); add --source-rows to transpose.
8
+
9
+ Examples:
10
+ python edf_granger_cli.py rec.edf -o out/
11
+ python edf_granger_cli.py rec.edf --lag 8 --edge-list --min-strength 1.3
12
+ See edf_effconn_cli.py for all options. Needs effconn.py next to the scripts.
13
+ """
14
+ import sys
15
+
16
+ from edf_effconn_cli import main
17
+
18
+ if __name__ == "__main__":
19
+ sys.exit(main(fixed_method="granger"))