dti-alps 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.
- dti_alps-0.1.0/PKG-INFO +140 -0
- dti_alps-0.1.0/README.md +111 -0
- dti_alps-0.1.0/dti_alps/__init__.py +8 -0
- dti_alps-0.1.0/dti_alps/__main__.py +319 -0
- dti_alps-0.1.0/dti_alps/gui/__init__.py +65 -0
- dti_alps-0.1.0/dti_alps/gui/app.py +2266 -0
- dti_alps-0.1.0/dti_alps/gui/config.py +251 -0
- dti_alps-0.1.0/dti_alps/gui/form_model.py +340 -0
- dti_alps-0.1.0/dti_alps/gui/report_model.py +296 -0
- dti_alps-0.1.0/dti_alps/gui/result_model.py +292 -0
- dti_alps-0.1.0/dti_alps/gui/user_config.py +154 -0
- dti_alps-0.1.0/dti_alps/gui/viewer.py +869 -0
- dti_alps-0.1.0/dti_alps/gui/viewer_model.py +600 -0
- dti_alps-0.1.0/dti_alps/processing/__init__.py +77 -0
- dti_alps-0.1.0/dti_alps/processing/alps_calculation.py +472 -0
- dti_alps-0.1.0/dti_alps/processing/b0_extraction.py +384 -0
- dti_alps-0.1.0/dti_alps/processing/batch.py +498 -0
- dti_alps-0.1.0/dti_alps/processing/commands.py +390 -0
- dti_alps-0.1.0/dti_alps/processing/constants.py +96 -0
- dti_alps-0.1.0/dti_alps/processing/discovery.py +467 -0
- dti_alps-0.1.0/dti_alps/processing/messages.py +107 -0
- dti_alps-0.1.0/dti_alps/processing/native_placement.py +347 -0
- dti_alps-0.1.0/dti_alps/processing/pipeline.py +760 -0
- dti_alps-0.1.0/dti_alps/processing/reanalysis.py +463 -0
- dti_alps-0.1.0/dti_alps/processing/registration/__init__.py +35 -0
- dti_alps-0.1.0/dti_alps/processing/registration/fsl.py +635 -0
- dti_alps-0.1.0/dti_alps/processing/registration/results.py +108 -0
- dti_alps-0.1.0/dti_alps/processing/report.py +589 -0
- dti_alps-0.1.0/dti_alps/processing/report_worker.py +127 -0
- dti_alps-0.1.0/dti_alps/processing/results_layout.py +451 -0
- dti_alps-0.1.0/dti_alps/processing/roi_placement.py +502 -0
- dti_alps-0.1.0/dti_alps/processing/staging.py +134 -0
- dti_alps-0.1.0/dti_alps/processing/state.py +372 -0
- dti_alps-0.1.0/dti_alps/processing/tool_runner.py +185 -0
- dti_alps-0.1.0/dti_alps/processing/validators.py +150 -0
- dti_alps-0.1.0/dti_alps/processing/workers.py +73 -0
- dti_alps-0.1.0/dti_alps/templates/JHU-labels-SCR-SLF.nii.gz +3 -0
- dti_alps-0.1.0/dti_alps/templates/JHU-labels-left_assoc.nii.gz +3 -0
- dti_alps-0.1.0/dti_alps/templates/JHU-labels-left_proj.nii.gz +3 -0
- dti_alps-0.1.0/dti_alps/templates/JHU-labels-right_assoc.nii.gz +3 -0
- dti_alps-0.1.0/dti_alps/templates/JHU-labels-right_proj.nii.gz +3 -0
- dti_alps-0.1.0/dti_alps.egg-info/PKG-INFO +140 -0
- dti_alps-0.1.0/dti_alps.egg-info/SOURCES.txt +69 -0
- dti_alps-0.1.0/dti_alps.egg-info/dependency_links.txt +1 -0
- dti_alps-0.1.0/dti_alps.egg-info/entry_points.txt +2 -0
- dti_alps-0.1.0/dti_alps.egg-info/requires.txt +15 -0
- dti_alps-0.1.0/dti_alps.egg-info/top_level.txt +1 -0
- dti_alps-0.1.0/pyproject.toml +111 -0
- dti_alps-0.1.0/setup.cfg +4 -0
- dti_alps-0.1.0/tests/test_alps_calculation.py +305 -0
- dti_alps-0.1.0/tests/test_app_logic.py +232 -0
- dti_alps-0.1.0/tests/test_batch_csv.py +205 -0
- dti_alps-0.1.0/tests/test_discovery.py +141 -0
- dti_alps-0.1.0/tests/test_engine_independence.py +76 -0
- dti_alps-0.1.0/tests/test_form_model.py +370 -0
- dti_alps-0.1.0/tests/test_messages.py +59 -0
- dti_alps-0.1.0/tests/test_native_placement_seam.py +200 -0
- dti_alps-0.1.0/tests/test_pipeline.py +270 -0
- dti_alps-0.1.0/tests/test_pipeline_seam.py +218 -0
- dti_alps-0.1.0/tests/test_reanalysis_csv.py +145 -0
- dti_alps-0.1.0/tests/test_reanalysis_seam.py +247 -0
- dti_alps-0.1.0/tests/test_registration.py +682 -0
- dti_alps-0.1.0/tests/test_registration_seam.py +116 -0
- dti_alps-0.1.0/tests/test_report_model.py +381 -0
- dti_alps-0.1.0/tests/test_result_model.py +392 -0
- dti_alps-0.1.0/tests/test_results_layout.py +539 -0
- dti_alps-0.1.0/tests/test_roi_catalog.py +35 -0
- dti_alps-0.1.0/tests/test_roi_placement.py +467 -0
- dti_alps-0.1.0/tests/test_staging.py +174 -0
- dti_alps-0.1.0/tests/test_tool_runner.py +94 -0
- dti_alps-0.1.0/tests/test_viewer_model.py +797 -0
dti_alps-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dti-alps
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automatic DTI-ALPS ROI detection and analysis
|
|
5
|
+
Author: DTI-ALPS Team
|
|
6
|
+
Project-URL: Repository, https://github.com/rmoskwa/auto-DTI-ALPS
|
|
7
|
+
Keywords: dti,alps,neuroimaging,mri,diffusion
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: nibabel
|
|
19
|
+
Requires-Dist: scipy
|
|
20
|
+
Provides-Extra: gui
|
|
21
|
+
Requires-Dist: PySide6; extra == "gui"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Provides-Extra: build
|
|
28
|
+
Requires-Dist: pyinstaller; extra == "build"
|
|
29
|
+
|
|
30
|
+
# autoDTI-ALPS
|
|
31
|
+
|
|
32
|
+
Automated DTI-ALPS (Diffusion Tensor Imaging Along the Perivascular Space) analysis tool. Uses template-based registration to place ROIs in projection and association fiber regions, then calculates the DTI-ALPS index from diffusion tensor imaging data.
|
|
33
|
+
|
|
34
|
+
## Dependencies
|
|
35
|
+
|
|
36
|
+
### Python
|
|
37
|
+
|
|
38
|
+
Requires Python 3.10+.
|
|
39
|
+
|
|
40
|
+
Core dependencies (installed automatically):
|
|
41
|
+
- [NumPy](https://numpy.org/)
|
|
42
|
+
- [NiBabel](https://nipy.org/nibabel/)
|
|
43
|
+
- [SciPy](https://scipy.org/)
|
|
44
|
+
|
|
45
|
+
Optional GUI dependencies (`pip install -e ".[gui]"`):
|
|
46
|
+
- [Matplotlib](https://matplotlib.org/)
|
|
47
|
+
- [Pillow](https://python-pillow.org/)
|
|
48
|
+
|
|
49
|
+
### External Neuroimaging Software
|
|
50
|
+
|
|
51
|
+
The following third-party programs must be installed and available on your system PATH.
|
|
52
|
+
|
|
53
|
+
#### MRtrix3 (required)
|
|
54
|
+
|
|
55
|
+
[MRtrix3](https://www.mrtrix.org/) provides tools for diffusion MRI preprocessing and tensor fitting.
|
|
56
|
+
|
|
57
|
+
| Command | Pipeline Stage | Purpose |
|
|
58
|
+
|---------|---------------|---------|
|
|
59
|
+
| `dwidenoise` | Denoising | Marchenko-Pastur PCA thermal noise removal |
|
|
60
|
+
| `mrdegibbs` | Gibbs Removal | Gibbs ringing artifact correction |
|
|
61
|
+
| `dwifslpreproc` | Preprocessing | Eddy current, motion, and distortion correction |
|
|
62
|
+
| `dwi2tensor` | Tensor Fitting | Fit diffusion tensor model to DWI data |
|
|
63
|
+
| `tensor2metric` | Metric Extraction | Extract FA, eigenvectors (V1-V3), and eigenvalues (L1-L3) |
|
|
64
|
+
| `dwi2mask` | Preprocessing | Brain mask generation from DWI |
|
|
65
|
+
| `dwiextract` | B0 Extraction | Extract b=0 volumes from DWI |
|
|
66
|
+
| `mrmath` | B0 Extraction | Average multiple b=0 volumes |
|
|
67
|
+
| `mrconvert` | Format Conversion | Image format conversion and header manipulation |
|
|
68
|
+
|
|
69
|
+
Installation: https://www.mrtrix.org/download/
|
|
70
|
+
|
|
71
|
+
#### FSL (required)
|
|
72
|
+
|
|
73
|
+
[FSL](https://fsl.fmrib.ox.ac.uk/fsl/) provides tools for brain extraction, registration, and image manipulation.
|
|
74
|
+
|
|
75
|
+
| Command | Pipeline Stage | Purpose |
|
|
76
|
+
|---------|---------------|---------|
|
|
77
|
+
| `flirt` | Registration | Linear (affine) FA-to-template registration |
|
|
78
|
+
| `fnirt` | Registration | Non-linear FA-to-template registration |
|
|
79
|
+
| `invwarp` | Registration | Generate inverse warp field for ROI transformation |
|
|
80
|
+
| `applywarp` | ROI Placement | Transform ROI templates from standard to native space |
|
|
81
|
+
| `fslmaths` | Masking | Apply brain mask to FA image |
|
|
82
|
+
| `eddy` | Preprocessing | Eddy current and motion correction |
|
|
83
|
+
| `topup` | Preprocessing | Susceptibility-induced distortion field estimation |
|
|
84
|
+
| `applytopup` | Preprocessing | Apply topup distortion correction |
|
|
85
|
+
|
|
86
|
+
Installation: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FslInstallation
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
Whichever route you choose, MRtrix3 and FSL are **not** bundled and must be
|
|
91
|
+
installed separately and on your `PATH` (see
|
|
92
|
+
[External Neuroimaging Software](#external-neuroimaging-software)).
|
|
93
|
+
|
|
94
|
+
### Install with pipx (recommended)
|
|
95
|
+
|
|
96
|
+
If you have Python 3.10+, [`pipx`](https://pipx.pypa.io/) installs the app into
|
|
97
|
+
an isolated environment and puts the `dti-alps` command on your `PATH`:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pipx install dti-alps
|
|
101
|
+
dti-alps # launch the GUI
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Update with `pipx upgrade dti-alps`. (Plain `pip install dti-alps` also works,
|
|
105
|
+
ideally inside a virtualenv, and provides the same `dti-alps` command.)
|
|
106
|
+
|
|
107
|
+
### Download the AppImage (no Python needed)
|
|
108
|
+
|
|
109
|
+
For double-click file with no Python setup, grab the latest Linux **AppImage** from the [Releases page](https://github.com/rmoskwa/auto-DTI-ALPS/releases):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
chmod +x dti-alps-*-x86_64.AppImage
|
|
113
|
+
./dti-alps-0.1.0-x86_64.AppImage
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The AppImage bundles the app and its Python dependencies. To update, download
|
|
117
|
+
the newer AppImage and replace the old file.
|
|
118
|
+
|
|
119
|
+
> **Qt runtime note:** the GUI uses Qt 6, which needs `libxcb-cursor0` on the
|
|
120
|
+
> host. If the app fails to start with an `xcb` platform-plugin error, install
|
|
121
|
+
> it: `sudo apt install libxcb-cursor0` (Debian/Ubuntu).
|
|
122
|
+
|
|
123
|
+
### From source (development)
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install -e ".[gui]"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Usage
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
dti-alps # Launch GUI (default)
|
|
133
|
+
dti-alps --viewer # Launch Results Viewer
|
|
134
|
+
dti-alps --viewer /path # Launch viewer with specific output folder
|
|
135
|
+
dti-alps --report /path # Generate quality reports
|
|
136
|
+
dti-alps --reanalyze /path --sphere 3 # Reanalyze with different ROI shapes
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
When running the AppImage, substitute `./dti-alps-*.AppImage` for `dti-alps`;
|
|
140
|
+
all CLI flags are forwarded (e.g. `./dti-alps-*.AppImage --viewer /path`).
|
dti_alps-0.1.0/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# autoDTI-ALPS
|
|
2
|
+
|
|
3
|
+
Automated DTI-ALPS (Diffusion Tensor Imaging Along the Perivascular Space) analysis tool. Uses template-based registration to place ROIs in projection and association fiber regions, then calculates the DTI-ALPS index from diffusion tensor imaging data.
|
|
4
|
+
|
|
5
|
+
## Dependencies
|
|
6
|
+
|
|
7
|
+
### Python
|
|
8
|
+
|
|
9
|
+
Requires Python 3.10+.
|
|
10
|
+
|
|
11
|
+
Core dependencies (installed automatically):
|
|
12
|
+
- [NumPy](https://numpy.org/)
|
|
13
|
+
- [NiBabel](https://nipy.org/nibabel/)
|
|
14
|
+
- [SciPy](https://scipy.org/)
|
|
15
|
+
|
|
16
|
+
Optional GUI dependencies (`pip install -e ".[gui]"`):
|
|
17
|
+
- [Matplotlib](https://matplotlib.org/)
|
|
18
|
+
- [Pillow](https://python-pillow.org/)
|
|
19
|
+
|
|
20
|
+
### External Neuroimaging Software
|
|
21
|
+
|
|
22
|
+
The following third-party programs must be installed and available on your system PATH.
|
|
23
|
+
|
|
24
|
+
#### MRtrix3 (required)
|
|
25
|
+
|
|
26
|
+
[MRtrix3](https://www.mrtrix.org/) provides tools for diffusion MRI preprocessing and tensor fitting.
|
|
27
|
+
|
|
28
|
+
| Command | Pipeline Stage | Purpose |
|
|
29
|
+
|---------|---------------|---------|
|
|
30
|
+
| `dwidenoise` | Denoising | Marchenko-Pastur PCA thermal noise removal |
|
|
31
|
+
| `mrdegibbs` | Gibbs Removal | Gibbs ringing artifact correction |
|
|
32
|
+
| `dwifslpreproc` | Preprocessing | Eddy current, motion, and distortion correction |
|
|
33
|
+
| `dwi2tensor` | Tensor Fitting | Fit diffusion tensor model to DWI data |
|
|
34
|
+
| `tensor2metric` | Metric Extraction | Extract FA, eigenvectors (V1-V3), and eigenvalues (L1-L3) |
|
|
35
|
+
| `dwi2mask` | Preprocessing | Brain mask generation from DWI |
|
|
36
|
+
| `dwiextract` | B0 Extraction | Extract b=0 volumes from DWI |
|
|
37
|
+
| `mrmath` | B0 Extraction | Average multiple b=0 volumes |
|
|
38
|
+
| `mrconvert` | Format Conversion | Image format conversion and header manipulation |
|
|
39
|
+
|
|
40
|
+
Installation: https://www.mrtrix.org/download/
|
|
41
|
+
|
|
42
|
+
#### FSL (required)
|
|
43
|
+
|
|
44
|
+
[FSL](https://fsl.fmrib.ox.ac.uk/fsl/) provides tools for brain extraction, registration, and image manipulation.
|
|
45
|
+
|
|
46
|
+
| Command | Pipeline Stage | Purpose |
|
|
47
|
+
|---------|---------------|---------|
|
|
48
|
+
| `flirt` | Registration | Linear (affine) FA-to-template registration |
|
|
49
|
+
| `fnirt` | Registration | Non-linear FA-to-template registration |
|
|
50
|
+
| `invwarp` | Registration | Generate inverse warp field for ROI transformation |
|
|
51
|
+
| `applywarp` | ROI Placement | Transform ROI templates from standard to native space |
|
|
52
|
+
| `fslmaths` | Masking | Apply brain mask to FA image |
|
|
53
|
+
| `eddy` | Preprocessing | Eddy current and motion correction |
|
|
54
|
+
| `topup` | Preprocessing | Susceptibility-induced distortion field estimation |
|
|
55
|
+
| `applytopup` | Preprocessing | Apply topup distortion correction |
|
|
56
|
+
|
|
57
|
+
Installation: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FslInstallation
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
Whichever route you choose, MRtrix3 and FSL are **not** bundled and must be
|
|
62
|
+
installed separately and on your `PATH` (see
|
|
63
|
+
[External Neuroimaging Software](#external-neuroimaging-software)).
|
|
64
|
+
|
|
65
|
+
### Install with pipx (recommended)
|
|
66
|
+
|
|
67
|
+
If you have Python 3.10+, [`pipx`](https://pipx.pypa.io/) installs the app into
|
|
68
|
+
an isolated environment and puts the `dti-alps` command on your `PATH`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pipx install dti-alps
|
|
72
|
+
dti-alps # launch the GUI
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Update with `pipx upgrade dti-alps`. (Plain `pip install dti-alps` also works,
|
|
76
|
+
ideally inside a virtualenv, and provides the same `dti-alps` command.)
|
|
77
|
+
|
|
78
|
+
### Download the AppImage (no Python needed)
|
|
79
|
+
|
|
80
|
+
For double-click file with no Python setup, grab the latest Linux **AppImage** from the [Releases page](https://github.com/rmoskwa/auto-DTI-ALPS/releases):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
chmod +x dti-alps-*-x86_64.AppImage
|
|
84
|
+
./dti-alps-0.1.0-x86_64.AppImage
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The AppImage bundles the app and its Python dependencies. To update, download
|
|
88
|
+
the newer AppImage and replace the old file.
|
|
89
|
+
|
|
90
|
+
> **Qt runtime note:** the GUI uses Qt 6, which needs `libxcb-cursor0` on the
|
|
91
|
+
> host. If the app fails to start with an `xcb` platform-plugin error, install
|
|
92
|
+
> it: `sudo apt install libxcb-cursor0` (Debian/Ubuntu).
|
|
93
|
+
|
|
94
|
+
### From source (development)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install -e ".[gui]"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
dti-alps # Launch GUI (default)
|
|
104
|
+
dti-alps --viewer # Launch Results Viewer
|
|
105
|
+
dti-alps --viewer /path # Launch viewer with specific output folder
|
|
106
|
+
dti-alps --report /path # Generate quality reports
|
|
107
|
+
dti-alps --reanalyze /path --sphere 3 # Reanalyze with different ROI shapes
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
When running the AppImage, substitute `./dti-alps-*.AppImage` for `dti-alps`;
|
|
111
|
+
all CLI flags are forwarded (e.g. `./dti-alps-*.AppImage --viewer /path`).
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point for python -m dti_alps
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m dti_alps # Launch GUI (default)
|
|
6
|
+
python -m dti_alps --gui # Launch GUI explicitly
|
|
7
|
+
python -m dti_alps --viewer # Launch Results Viewer
|
|
8
|
+
python -m dti_alps --viewer /path/to/output # Launch viewer with folder
|
|
9
|
+
python -m dti_alps --report /path/to/output # Generate quality reports
|
|
10
|
+
|
|
11
|
+
ROI Reanalysis (post-processing with different ROI shapes):
|
|
12
|
+
python -m dti_alps --reanalyze /path/to/output --sphere 3.0
|
|
13
|
+
python -m dti_alps --reanalyze /path/to/output --squarev9
|
|
14
|
+
python -m dti_alps --reanalyze /path/to/output --squarev4
|
|
15
|
+
python -m dti_alps --reanalyze /path/to/output --sphere 2.5 --adaptive
|
|
16
|
+
python -m dti_alps --reanalyze /path/to/output --sphere 2,3,4
|
|
17
|
+
python -m dti_alps --reanalyze /path/to/output --sphere 3 --squarev4
|
|
18
|
+
|
|
19
|
+
Output naming:
|
|
20
|
+
Without --adaptive: rois_{shape}/ and alps_results_{shape}.csv
|
|
21
|
+
With --adaptive: rois_{shape}_adaptive/ and alps_results_{shape}_adaptive.csv
|
|
22
|
+
The default 3 mm sphere collapses to the bare rois/ and alps_results.csv.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
--sphere 3 -> rois/, alps_results.csv
|
|
26
|
+
--sphere 3 --adaptive -> rois_adaptive/, alps_results_rois_adaptive.csv
|
|
27
|
+
--squarev9 -> rois_squarev9/, alps_results_squarev9.csv
|
|
28
|
+
--squarev9 --adaptive -> rois_squarev9_adaptive/, alps_results_squarev9_adaptive.csv
|
|
29
|
+
--squarev4 -> rois_squarev4/, alps_results_squarev4.csv
|
|
30
|
+
--sphere 2.5 -> rois_sphere2p5/, alps_results_sphere2p5.csv
|
|
31
|
+
--sphere 2.5 --adaptive -> rois_sphere2p5_adaptive/, alps_results_sphere2p5_adaptive.csv
|
|
32
|
+
|
|
33
|
+
Quality Report Generation:
|
|
34
|
+
python -m dti_alps --report /path/to/output
|
|
35
|
+
Generates quality_report_{shape}.csv for each ROI shape found.
|
|
36
|
+
Reports include:
|
|
37
|
+
- Directional Alignment (V1): How well fibers align with expected direction
|
|
38
|
+
- Angular Dispersion (V1): Standard deviation of fiber angles
|
|
39
|
+
- Fractional Anisotropy: Mean FA within each ROI
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
import argparse
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
from .processing.constants import ADAPTIVE_SEARCH_RANGE, ROI_SPHERE_RADIUS_RANGE
|
|
46
|
+
|
|
47
|
+
# Sphere radius validation bounds, read from the engine's single source of truth.
|
|
48
|
+
SPHERE_RADIUS_MIN, SPHERE_RADIUS_MAX = ROI_SPHERE_RADIUS_RANGE
|
|
49
|
+
|
|
50
|
+
# Adaptive search envelope bounds, from the same single source of truth the GUI
|
|
51
|
+
# and the AdaptiveSearchConfig guard use, so the three cannot drift apart.
|
|
52
|
+
SEARCH_MIN, SEARCH_MAX = ADAPTIVE_SEARCH_RANGE
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _validate_search_value(value: str) -> int:
|
|
56
|
+
"""Validate an adaptive-search flag is an int within the allowed range."""
|
|
57
|
+
try:
|
|
58
|
+
parsed = int(value)
|
|
59
|
+
except ValueError as err:
|
|
60
|
+
raise argparse.ArgumentTypeError(f"invalid int value: '{value}'") from err
|
|
61
|
+
|
|
62
|
+
if parsed < SEARCH_MIN or parsed > SEARCH_MAX:
|
|
63
|
+
raise argparse.ArgumentTypeError(
|
|
64
|
+
f"must be between {SEARCH_MIN} and {SEARCH_MAX}, got {parsed}"
|
|
65
|
+
)
|
|
66
|
+
return parsed
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _validate_sphere_radii(value: str) -> list[float]:
|
|
70
|
+
"""Validate comma-separated sphere radii are within allowed range."""
|
|
71
|
+
radii = []
|
|
72
|
+
for part in value.split(","):
|
|
73
|
+
part = part.strip()
|
|
74
|
+
try:
|
|
75
|
+
radius = float(part)
|
|
76
|
+
except ValueError as err:
|
|
77
|
+
raise argparse.ArgumentTypeError(f"invalid float value: '{part}'") from err
|
|
78
|
+
|
|
79
|
+
if radius < SPHERE_RADIUS_MIN or radius > SPHERE_RADIUS_MAX:
|
|
80
|
+
raise argparse.ArgumentTypeError(
|
|
81
|
+
f"radius must be between {SPHERE_RADIUS_MIN} and {SPHERE_RADIUS_MAX} mm, "
|
|
82
|
+
f"got {radius}"
|
|
83
|
+
)
|
|
84
|
+
radii.append(radius)
|
|
85
|
+
return radii
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _parse_reanalysis_args() -> argparse.Namespace:
|
|
89
|
+
"""Parse command line arguments for reanalysis mode."""
|
|
90
|
+
from .processing.constants import FA_THRESHOLD, AdaptiveSearchConfig
|
|
91
|
+
|
|
92
|
+
search_defaults = AdaptiveSearchConfig()
|
|
93
|
+
|
|
94
|
+
parser = argparse.ArgumentParser(
|
|
95
|
+
description="DTI-ALPS ROI Reanalysis",
|
|
96
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
97
|
+
epilog="""
|
|
98
|
+
Examples:
|
|
99
|
+
%(prog)s --reanalyze /path/to/output --sphere 3.0
|
|
100
|
+
Reanalyze with 3mm radius spherical ROIs
|
|
101
|
+
|
|
102
|
+
%(prog)s --reanalyze /path/to/output --squarev9
|
|
103
|
+
Reanalyze with 3x3 voxel square ROIs (9 voxels)
|
|
104
|
+
|
|
105
|
+
%(prog)s --reanalyze /path/to/output --squarev4
|
|
106
|
+
Reanalyze with 2x2 voxel square ROIs (4 voxels, V1-optimized)
|
|
107
|
+
|
|
108
|
+
%(prog)s --reanalyze /path/to/output --sphere 2.5 --adaptive
|
|
109
|
+
Reanalyze with 2.5mm spheres and adaptive ROI placement enabled
|
|
110
|
+
|
|
111
|
+
%(prog)s --reanalyze /path/to/output --sphere 2,3,4
|
|
112
|
+
Reanalyze with 2mm, 3mm, and 4mm spheres in one run
|
|
113
|
+
|
|
114
|
+
%(prog)s --reanalyze /path/to/output --sphere 3 --squarev4
|
|
115
|
+
Reanalyze with both 3mm sphere and 2x2 square ROIs
|
|
116
|
+
|
|
117
|
+
%(prog)s --reanalyze /path/to/output --squarev9 --adaptive --method ALPS-LAB
|
|
118
|
+
Reanalyze with square ROIs, adaptive placement, and only ALPS-LAB calculation
|
|
119
|
+
""",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
parser.add_argument(
|
|
123
|
+
"--reanalyze",
|
|
124
|
+
metavar="OUTPUT_DIR",
|
|
125
|
+
required=True,
|
|
126
|
+
help="Path to output directory containing processed subjects",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# ROI shape options (can be combined)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--sphere",
|
|
132
|
+
type=_validate_sphere_radii,
|
|
133
|
+
metavar="RADIUS[,RADIUS,...]",
|
|
134
|
+
help=(
|
|
135
|
+
f"Create spherical ROIs with given radius/radii "
|
|
136
|
+
f"({SPHERE_RADIUS_MIN}-{SPHERE_RADIUS_MAX} mm). "
|
|
137
|
+
f"Comma-separated for multiple (e.g., --sphere 2,3,4)"
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
"--squarev9",
|
|
142
|
+
action="store_true",
|
|
143
|
+
help="Create 3x3 voxel square ROIs in the axial plane (9 voxels total)",
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"--squarev4",
|
|
147
|
+
action="store_true",
|
|
148
|
+
help="Create 2x2 voxel square ROIs in the axial plane (4 voxels, V1-optimized)",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
"--adaptive",
|
|
153
|
+
action="store_true",
|
|
154
|
+
help="Enable adaptive ROI placement based on fiber orientation",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Adaptive search envelope. Each is validated to the shared 1-4 range and
|
|
158
|
+
# defaults to the historical value; all are inert without --adaptive
|
|
159
|
+
# (Standard placement runs no search).
|
|
160
|
+
search_help_suffix = f"(±voxels, {SEARCH_MIN}-{SEARCH_MAX}, only with --adaptive)"
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"--search-x",
|
|
163
|
+
type=_validate_search_value,
|
|
164
|
+
default=search_defaults.search_x,
|
|
165
|
+
metavar="N",
|
|
166
|
+
help=f"Adaptive search window in X {search_help_suffix}",
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"--search-y",
|
|
170
|
+
type=_validate_search_value,
|
|
171
|
+
default=search_defaults.search_y,
|
|
172
|
+
metavar="N",
|
|
173
|
+
help=f"Adaptive search window in Y {search_help_suffix}",
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
"--search-z",
|
|
177
|
+
type=_validate_search_value,
|
|
178
|
+
default=search_defaults.search_z,
|
|
179
|
+
metavar="N",
|
|
180
|
+
help=f"Adaptive search window in Z {search_help_suffix}",
|
|
181
|
+
)
|
|
182
|
+
parser.add_argument(
|
|
183
|
+
"--max-y-drift",
|
|
184
|
+
type=_validate_search_value,
|
|
185
|
+
default=search_defaults.max_y_drift,
|
|
186
|
+
metavar="N",
|
|
187
|
+
help=f"Max association-ROI Y drift from projection ROI {search_help_suffix}",
|
|
188
|
+
)
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
"--max-z-drift",
|
|
191
|
+
type=_validate_search_value,
|
|
192
|
+
default=search_defaults.max_z_drift,
|
|
193
|
+
metavar="N",
|
|
194
|
+
help=f"Max association-ROI Z drift from projection ROI {search_help_suffix}",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
parser.add_argument(
|
|
198
|
+
"--method",
|
|
199
|
+
choices=["ALPS-LAB", "ALPS-PAS", "Both"],
|
|
200
|
+
default="Both",
|
|
201
|
+
help="ALPS calculation method (default: Both)",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
parser.add_argument(
|
|
205
|
+
"--fa-threshold",
|
|
206
|
+
type=float,
|
|
207
|
+
default=FA_THRESHOLD,
|
|
208
|
+
metavar="THRESHOLD",
|
|
209
|
+
help=f"FA threshold for filtering CSF voxels (default: {FA_THRESHOLD})",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return parser.parse_args()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _run_reanalysis() -> None:
|
|
216
|
+
"""Run ROI reanalysis from command line arguments."""
|
|
217
|
+
args = _parse_reanalysis_args()
|
|
218
|
+
|
|
219
|
+
from .processing.constants import AdaptiveSearchConfig
|
|
220
|
+
from .processing.reanalysis import ROIShape, run_reanalysis
|
|
221
|
+
|
|
222
|
+
# Assemble the envelope from the (validated, defaulted) flags. The 1-4 guard
|
|
223
|
+
# already fired during parse; this construction cannot raise. Inert unless
|
|
224
|
+
# --adaptive is set.
|
|
225
|
+
search = AdaptiveSearchConfig(
|
|
226
|
+
search_x=args.search_x,
|
|
227
|
+
search_y=args.search_y,
|
|
228
|
+
search_z=args.search_z,
|
|
229
|
+
max_y_drift=args.max_y_drift,
|
|
230
|
+
max_z_drift=args.max_z_drift,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Build list of ROI shapes from all specified flags
|
|
234
|
+
roi_shapes: list[ROIShape] = []
|
|
235
|
+
if args.sphere:
|
|
236
|
+
for radius in args.sphere:
|
|
237
|
+
roi_shapes.append(ROIShape(shape_type="sphere", sphere_radius=radius))
|
|
238
|
+
if args.squarev9:
|
|
239
|
+
roi_shapes.append(ROIShape(shape_type="squarev9"))
|
|
240
|
+
if args.squarev4:
|
|
241
|
+
roi_shapes.append(ROIShape(shape_type="squarev4"))
|
|
242
|
+
|
|
243
|
+
if not roi_shapes:
|
|
244
|
+
print(
|
|
245
|
+
"ERROR: At least one ROI shape must be specified (--sphere, --squarev9, or --squarev4)"
|
|
246
|
+
)
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
# Run reanalysis for each shape
|
|
250
|
+
for roi_shape in roi_shapes:
|
|
251
|
+
if len(roi_shapes) > 1:
|
|
252
|
+
print(f"\n{'=' * 60}")
|
|
253
|
+
print(f"Reanalysis: {roi_shape.name}")
|
|
254
|
+
print(f"{'=' * 60}\n")
|
|
255
|
+
|
|
256
|
+
run_reanalysis(
|
|
257
|
+
output_dir=args.reanalyze,
|
|
258
|
+
roi_shape=roi_shape,
|
|
259
|
+
enable_adaptive=args.adaptive,
|
|
260
|
+
alps_method=args.method,
|
|
261
|
+
fa_threshold=args.fa_threshold,
|
|
262
|
+
search=search,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def main():
|
|
267
|
+
"""Main entry point that dispatches to GUI, viewer, report, or reanalysis."""
|
|
268
|
+
# Check for reanalysis mode first (needs argparse)
|
|
269
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "--reanalyze":
|
|
270
|
+
_run_reanalysis()
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
# Check if viewer mode
|
|
274
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "--viewer":
|
|
275
|
+
# Validate Qt up front so a missing PySide6 fails with a clear,
|
|
276
|
+
# actionable message instead of a raw import traceback (Decision 7).
|
|
277
|
+
from .gui import _check_viewer_dependencies
|
|
278
|
+
|
|
279
|
+
_check_viewer_dependencies()
|
|
280
|
+
from .gui.viewer import launch_viewer
|
|
281
|
+
|
|
282
|
+
# Check if output folder path was provided
|
|
283
|
+
output_folder = sys.argv[2] if len(sys.argv) > 2 else None
|
|
284
|
+
launch_viewer(output_folder)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
# Check if report mode
|
|
288
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "--report":
|
|
289
|
+
from .processing.report import run_report
|
|
290
|
+
|
|
291
|
+
if len(sys.argv) < 3:
|
|
292
|
+
print("ERROR: --report requires an output directory path")
|
|
293
|
+
print("Usage: python -m dti_alps --report /path/to/output")
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
|
|
296
|
+
output_folder = sys.argv[2]
|
|
297
|
+
run_report(output_folder)
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# Check for help on reanalysis
|
|
301
|
+
if len(sys.argv) >= 2 and sys.argv[1] in ["--help", "-h"]:
|
|
302
|
+
print(__doc__)
|
|
303
|
+
print("\nFor reanalysis options, use: python -m dti_alps --reanalyze --help")
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
# GUI mode: launched explicitly with --gui, or as the default with no args.
|
|
307
|
+
# Reject anything else rather than silently launching the GUI.
|
|
308
|
+
if len(sys.argv) >= 2 and sys.argv[1] != "--gui":
|
|
309
|
+
print(f"ERROR: unknown option '{sys.argv[1]}'")
|
|
310
|
+
print(__doc__)
|
|
311
|
+
sys.exit(2)
|
|
312
|
+
|
|
313
|
+
from .gui import main as gui_main
|
|
314
|
+
|
|
315
|
+
gui_main()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DTI-ALPS Processing GUI
|
|
3
|
+
|
|
4
|
+
A PySide6 (Qt) graphical interface for end-to-end DTI-ALPS analysis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _check_science_deps():
|
|
11
|
+
"""Check for the numpy/nibabel/scipy science stack (toolkit-independent).
|
|
12
|
+
|
|
13
|
+
Factored out of :func:`_check_dependencies` so the viewer entry point can
|
|
14
|
+
validate the science stack independently (PRD 0013, Decision 12).
|
|
15
|
+
"""
|
|
16
|
+
import importlib.util
|
|
17
|
+
|
|
18
|
+
missing_packages = []
|
|
19
|
+
for pkg in ["nibabel", "numpy", "scipy"]:
|
|
20
|
+
if importlib.util.find_spec(pkg) is None:
|
|
21
|
+
missing_packages.append(pkg)
|
|
22
|
+
|
|
23
|
+
if missing_packages:
|
|
24
|
+
print(f"Error: Required packages not found: {', '.join(missing_packages)}")
|
|
25
|
+
print("Please install: pip install nibabel numpy scipy")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _check_viewer_dependencies():
|
|
30
|
+
"""Check for PySide6 (Qt), required by the whole GUI (main window + viewer).
|
|
31
|
+
|
|
32
|
+
Named for its original PRD 0010 role (Qt was first required only by the
|
|
33
|
+
viewer); since the PRD 0013 flip the main window is Qt too, so this is the
|
|
34
|
+
GUI-wide Qt check.
|
|
35
|
+
"""
|
|
36
|
+
import importlib.util
|
|
37
|
+
|
|
38
|
+
if importlib.util.find_spec("PySide6") is None:
|
|
39
|
+
print("Error: PySide6 is required by the DTI-ALPS GUI but not installed.")
|
|
40
|
+
print('Please install: pip install "dti-alps[gui]" (or: pip install PySide6)')
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _check_dependencies():
|
|
45
|
+
"""Check for required GUI dependencies (PySide6 + the science stack)."""
|
|
46
|
+
_check_viewer_dependencies()
|
|
47
|
+
_check_science_deps()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
"""Launch the DTI-ALPS GUI application."""
|
|
52
|
+
_check_dependencies()
|
|
53
|
+
|
|
54
|
+
from .app import launch_app
|
|
55
|
+
|
|
56
|
+
launch_app()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def viewer(output_folder: str | None = None):
|
|
60
|
+
"""Launch the DTI-ALPS Results Viewer."""
|
|
61
|
+
_check_dependencies()
|
|
62
|
+
|
|
63
|
+
from .viewer import launch_viewer
|
|
64
|
+
|
|
65
|
+
launch_viewer(output_folder)
|