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.
Files changed (71) hide show
  1. dti_alps-0.1.0/PKG-INFO +140 -0
  2. dti_alps-0.1.0/README.md +111 -0
  3. dti_alps-0.1.0/dti_alps/__init__.py +8 -0
  4. dti_alps-0.1.0/dti_alps/__main__.py +319 -0
  5. dti_alps-0.1.0/dti_alps/gui/__init__.py +65 -0
  6. dti_alps-0.1.0/dti_alps/gui/app.py +2266 -0
  7. dti_alps-0.1.0/dti_alps/gui/config.py +251 -0
  8. dti_alps-0.1.0/dti_alps/gui/form_model.py +340 -0
  9. dti_alps-0.1.0/dti_alps/gui/report_model.py +296 -0
  10. dti_alps-0.1.0/dti_alps/gui/result_model.py +292 -0
  11. dti_alps-0.1.0/dti_alps/gui/user_config.py +154 -0
  12. dti_alps-0.1.0/dti_alps/gui/viewer.py +869 -0
  13. dti_alps-0.1.0/dti_alps/gui/viewer_model.py +600 -0
  14. dti_alps-0.1.0/dti_alps/processing/__init__.py +77 -0
  15. dti_alps-0.1.0/dti_alps/processing/alps_calculation.py +472 -0
  16. dti_alps-0.1.0/dti_alps/processing/b0_extraction.py +384 -0
  17. dti_alps-0.1.0/dti_alps/processing/batch.py +498 -0
  18. dti_alps-0.1.0/dti_alps/processing/commands.py +390 -0
  19. dti_alps-0.1.0/dti_alps/processing/constants.py +96 -0
  20. dti_alps-0.1.0/dti_alps/processing/discovery.py +467 -0
  21. dti_alps-0.1.0/dti_alps/processing/messages.py +107 -0
  22. dti_alps-0.1.0/dti_alps/processing/native_placement.py +347 -0
  23. dti_alps-0.1.0/dti_alps/processing/pipeline.py +760 -0
  24. dti_alps-0.1.0/dti_alps/processing/reanalysis.py +463 -0
  25. dti_alps-0.1.0/dti_alps/processing/registration/__init__.py +35 -0
  26. dti_alps-0.1.0/dti_alps/processing/registration/fsl.py +635 -0
  27. dti_alps-0.1.0/dti_alps/processing/registration/results.py +108 -0
  28. dti_alps-0.1.0/dti_alps/processing/report.py +589 -0
  29. dti_alps-0.1.0/dti_alps/processing/report_worker.py +127 -0
  30. dti_alps-0.1.0/dti_alps/processing/results_layout.py +451 -0
  31. dti_alps-0.1.0/dti_alps/processing/roi_placement.py +502 -0
  32. dti_alps-0.1.0/dti_alps/processing/staging.py +134 -0
  33. dti_alps-0.1.0/dti_alps/processing/state.py +372 -0
  34. dti_alps-0.1.0/dti_alps/processing/tool_runner.py +185 -0
  35. dti_alps-0.1.0/dti_alps/processing/validators.py +150 -0
  36. dti_alps-0.1.0/dti_alps/processing/workers.py +73 -0
  37. dti_alps-0.1.0/dti_alps/templates/JHU-labels-SCR-SLF.nii.gz +3 -0
  38. dti_alps-0.1.0/dti_alps/templates/JHU-labels-left_assoc.nii.gz +3 -0
  39. dti_alps-0.1.0/dti_alps/templates/JHU-labels-left_proj.nii.gz +3 -0
  40. dti_alps-0.1.0/dti_alps/templates/JHU-labels-right_assoc.nii.gz +3 -0
  41. dti_alps-0.1.0/dti_alps/templates/JHU-labels-right_proj.nii.gz +3 -0
  42. dti_alps-0.1.0/dti_alps.egg-info/PKG-INFO +140 -0
  43. dti_alps-0.1.0/dti_alps.egg-info/SOURCES.txt +69 -0
  44. dti_alps-0.1.0/dti_alps.egg-info/dependency_links.txt +1 -0
  45. dti_alps-0.1.0/dti_alps.egg-info/entry_points.txt +2 -0
  46. dti_alps-0.1.0/dti_alps.egg-info/requires.txt +15 -0
  47. dti_alps-0.1.0/dti_alps.egg-info/top_level.txt +1 -0
  48. dti_alps-0.1.0/pyproject.toml +111 -0
  49. dti_alps-0.1.0/setup.cfg +4 -0
  50. dti_alps-0.1.0/tests/test_alps_calculation.py +305 -0
  51. dti_alps-0.1.0/tests/test_app_logic.py +232 -0
  52. dti_alps-0.1.0/tests/test_batch_csv.py +205 -0
  53. dti_alps-0.1.0/tests/test_discovery.py +141 -0
  54. dti_alps-0.1.0/tests/test_engine_independence.py +76 -0
  55. dti_alps-0.1.0/tests/test_form_model.py +370 -0
  56. dti_alps-0.1.0/tests/test_messages.py +59 -0
  57. dti_alps-0.1.0/tests/test_native_placement_seam.py +200 -0
  58. dti_alps-0.1.0/tests/test_pipeline.py +270 -0
  59. dti_alps-0.1.0/tests/test_pipeline_seam.py +218 -0
  60. dti_alps-0.1.0/tests/test_reanalysis_csv.py +145 -0
  61. dti_alps-0.1.0/tests/test_reanalysis_seam.py +247 -0
  62. dti_alps-0.1.0/tests/test_registration.py +682 -0
  63. dti_alps-0.1.0/tests/test_registration_seam.py +116 -0
  64. dti_alps-0.1.0/tests/test_report_model.py +381 -0
  65. dti_alps-0.1.0/tests/test_result_model.py +392 -0
  66. dti_alps-0.1.0/tests/test_results_layout.py +539 -0
  67. dti_alps-0.1.0/tests/test_roi_catalog.py +35 -0
  68. dti_alps-0.1.0/tests/test_roi_placement.py +467 -0
  69. dti_alps-0.1.0/tests/test_staging.py +174 -0
  70. dti_alps-0.1.0/tests/test_tool_runner.py +94 -0
  71. dti_alps-0.1.0/tests/test_viewer_model.py +797 -0
@@ -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`).
@@ -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,8 @@
1
+ """
2
+ DTI-ALPS - Automated DTI-ALPS Processing Pipeline
3
+
4
+ This package provides tools for automated DTI-ALPS (Diffusion Tensor Imaging
5
+ Along the Perivascular Space) analysis using template-based ROI placement.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -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)