voxelops 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 (156) hide show
  1. voxelops-0.1.0/.claude/settings.local.json +18 -0
  2. voxelops-0.1.0/.github/workflows/ci.yml +47 -0
  3. voxelops-0.1.0/.gitignore +56 -0
  4. voxelops-0.1.0/.pre-commit-config.yaml +18 -0
  5. voxelops-0.1.0/.python-version +1 -0
  6. voxelops-0.1.0/.readthedocs.yaml +20 -0
  7. voxelops-0.1.0/.uvignore +13 -0
  8. voxelops-0.1.0/ARCHITECTURE.md +217 -0
  9. voxelops-0.1.0/LICENSE +21 -0
  10. voxelops-0.1.0/Makefile +130 -0
  11. voxelops-0.1.0/PKG-INFO +221 -0
  12. voxelops-0.1.0/README.rst +176 -0
  13. voxelops-0.1.0/UV_QUICKSTART.md +381 -0
  14. voxelops-0.1.0/docs/Makefile +20 -0
  15. voxelops-0.1.0/docs/_build/doctrees/contributing.doctree +0 -0
  16. voxelops-0.1.0/docs/_build/doctrees/environment.pickle +0 -0
  17. voxelops-0.1.0/docs/_build/doctrees/getting_started.doctree +0 -0
  18. voxelops-0.1.0/docs/_build/doctrees/index.doctree +0 -0
  19. voxelops-0.1.0/docs/_build/doctrees/source/modules.doctree +0 -0
  20. voxelops-0.1.0/docs/_build/doctrees/source/voxelops.doctree +0 -0
  21. voxelops-0.1.0/docs/_build/doctrees/source/voxelops.runners.doctree +0 -0
  22. voxelops-0.1.0/docs/_build/doctrees/source/voxelops.schemas.doctree +0 -0
  23. voxelops-0.1.0/docs/_build/doctrees/source/voxelops.utils.doctree +0 -0
  24. voxelops-0.1.0/docs/_build/doctrees/tutorials.doctree +0 -0
  25. voxelops-0.1.0/docs/_build/doctrees/workflows.doctree +0 -0
  26. voxelops-0.1.0/docs/_build/html/.buildinfo +4 -0
  27. voxelops-0.1.0/docs/_build/html/_images/Gemini_Generated_Image_m9bi47m9bi47m9bi.png +0 -0
  28. voxelops-0.1.0/docs/_build/html/_modules/index.html +121 -0
  29. voxelops-0.1.0/docs/_build/html/_modules/voxelops/exceptions.html +295 -0
  30. voxelops-0.1.0/docs/_build/html/_modules/voxelops/runners/_base.html +311 -0
  31. voxelops-0.1.0/docs/_build/html/_modules/voxelops/runners/heudiconv.html +296 -0
  32. voxelops-0.1.0/docs/_build/html/_modules/voxelops/runners/qsiparc.html +263 -0
  33. voxelops-0.1.0/docs/_build/html/_modules/voxelops/runners/qsiprep.html +287 -0
  34. voxelops-0.1.0/docs/_build/html/_modules/voxelops/runners/qsirecon.html +285 -0
  35. voxelops-0.1.0/docs/_build/html/_modules/voxelops/schemas/heudiconv.html +247 -0
  36. voxelops-0.1.0/docs/_build/html/_modules/voxelops/schemas/qsiparc.html +230 -0
  37. voxelops-0.1.0/docs/_build/html/_modules/voxelops/schemas/qsiprep.html +266 -0
  38. voxelops-0.1.0/docs/_build/html/_modules/voxelops/schemas/qsirecon.html +280 -0
  39. voxelops-0.1.0/docs/_build/html/_modules/voxelops/utils/bids.html +584 -0
  40. voxelops-0.1.0/docs/_build/html/_sources/contributing.rst.txt +211 -0
  41. voxelops-0.1.0/docs/_build/html/_sources/getting_started.rst.txt +83 -0
  42. voxelops-0.1.0/docs/_build/html/_sources/index.rst.txt +105 -0
  43. voxelops-0.1.0/docs/_build/html/_sources/source/modules.rst.txt +7 -0
  44. voxelops-0.1.0/docs/_build/html/_sources/source/voxelops.rst.txt +27 -0
  45. voxelops-0.1.0/docs/_build/html/_sources/source/voxelops.runners.rst.txt +54 -0
  46. voxelops-0.1.0/docs/_build/html/_sources/source/voxelops.schemas.rst.txt +46 -0
  47. voxelops-0.1.0/docs/_build/html/_sources/source/voxelops.utils.rst.txt +23 -0
  48. voxelops-0.1.0/docs/_build/html/_sources/tutorials.rst.txt +23 -0
  49. voxelops-0.1.0/docs/_build/html/_sources/workflows.rst.txt +77 -0
  50. voxelops-0.1.0/docs/_build/html/_static/Gemini_Generated_Image_m9bi47m9bi47m9bi.png +0 -0
  51. voxelops-0.1.0/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js +123 -0
  52. voxelops-0.1.0/docs/_build/html/_static/basic.css +925 -0
  53. voxelops-0.1.0/docs/_build/html/_static/css/badge_only.css +1 -0
  54. voxelops-0.1.0/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  55. voxelops-0.1.0/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  56. voxelops-0.1.0/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  57. voxelops-0.1.0/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  58. voxelops-0.1.0/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot +0 -0
  59. voxelops-0.1.0/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg +2671 -0
  60. voxelops-0.1.0/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  61. voxelops-0.1.0/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff +0 -0
  62. voxelops-0.1.0/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  63. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-bold-italic.woff +0 -0
  64. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  65. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-bold.woff +0 -0
  66. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-bold.woff2 +0 -0
  67. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-normal-italic.woff +0 -0
  68. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  69. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-normal.woff +0 -0
  70. voxelops-0.1.0/docs/_build/html/_static/css/fonts/lato-normal.woff2 +0 -0
  71. voxelops-0.1.0/docs/_build/html/_static/css/theme.css +4 -0
  72. voxelops-0.1.0/docs/_build/html/_static/doctools.js +156 -0
  73. voxelops-0.1.0/docs/_build/html/_static/documentation_options.js +13 -0
  74. voxelops-0.1.0/docs/_build/html/_static/file.png +0 -0
  75. voxelops-0.1.0/docs/_build/html/_static/jquery.js +2 -0
  76. voxelops-0.1.0/docs/_build/html/_static/js/badge_only.js +1 -0
  77. voxelops-0.1.0/docs/_build/html/_static/js/html5shiv-printshiv.min.js +4 -0
  78. voxelops-0.1.0/docs/_build/html/_static/js/html5shiv.min.js +4 -0
  79. voxelops-0.1.0/docs/_build/html/_static/js/theme.js +1 -0
  80. voxelops-0.1.0/docs/_build/html/_static/language_data.js +199 -0
  81. voxelops-0.1.0/docs/_build/html/_static/minus.png +0 -0
  82. voxelops-0.1.0/docs/_build/html/_static/plus.png +0 -0
  83. voxelops-0.1.0/docs/_build/html/_static/pygments.css +75 -0
  84. voxelops-0.1.0/docs/_build/html/_static/searchtools.js +620 -0
  85. voxelops-0.1.0/docs/_build/html/_static/sphinx_highlight.js +154 -0
  86. voxelops-0.1.0/docs/_build/html/contributing.html +306 -0
  87. voxelops-0.1.0/docs/_build/html/genindex.html +683 -0
  88. voxelops-0.1.0/docs/_build/html/getting_started.html +197 -0
  89. voxelops-0.1.0/docs/_build/html/index.html +233 -0
  90. voxelops-0.1.0/docs/_build/html/objects.inv +0 -0
  91. voxelops-0.1.0/docs/_build/html/py-modindex.html +200 -0
  92. voxelops-0.1.0/docs/_build/html/search.html +130 -0
  93. voxelops-0.1.0/docs/_build/html/searchindex.js +1 -0
  94. voxelops-0.1.0/docs/_build/html/source/modules.html +167 -0
  95. voxelops-0.1.0/docs/_build/html/source/voxelops.html +470 -0
  96. voxelops-0.1.0/docs/_build/html/source/voxelops.runners.html +452 -0
  97. voxelops-0.1.0/docs/_build/html/source/voxelops.schemas.html +846 -0
  98. voxelops-0.1.0/docs/_build/html/source/voxelops.utils.html +251 -0
  99. voxelops-0.1.0/docs/_build/html/tutorials.html +136 -0
  100. voxelops-0.1.0/docs/_build/html/workflows.html +186 -0
  101. voxelops-0.1.0/docs/conf.py +74 -0
  102. voxelops-0.1.0/docs/contributing.rst +211 -0
  103. voxelops-0.1.0/docs/getting_started.rst +83 -0
  104. voxelops-0.1.0/docs/images/Gemini_Generated_Image_m9bi47m9bi47m9bi.png +0 -0
  105. voxelops-0.1.0/docs/index.rst +109 -0
  106. voxelops-0.1.0/docs/make.bat +35 -0
  107. voxelops-0.1.0/docs/requirements.txt +3 -0
  108. voxelops-0.1.0/docs/source/modules.rst +7 -0
  109. voxelops-0.1.0/docs/source/voxelops.rst +27 -0
  110. voxelops-0.1.0/docs/source/voxelops.runners.rst +54 -0
  111. voxelops-0.1.0/docs/source/voxelops.schemas.rst +46 -0
  112. voxelops-0.1.0/docs/source/voxelops.utils.rst +23 -0
  113. voxelops-0.1.0/docs/tutorials.rst +23 -0
  114. voxelops-0.1.0/docs/workflows.rst +77 -0
  115. voxelops-0.1.0/examples/dicom_to_bids.py +35 -0
  116. voxelops-0.1.0/examples/qsiparc.py +37 -0
  117. voxelops-0.1.0/examples/qsiprep.py +35 -0
  118. voxelops-0.1.0/examples/qsirecon.py +36 -0
  119. voxelops-0.1.0/notebooks/.ruff.toml +2 -0
  120. voxelops-0.1.0/notebooks/01_heudiconv_basics.ipynb +370 -0
  121. voxelops-0.1.0/notebooks/02_qsiprep_basics.ipynb +570 -0
  122. voxelops-0.1.0/notebooks/03_qsirecon_basics.ipynb +586 -0
  123. voxelops-0.1.0/notebooks/04_qsiparc_basics.ipynb +521 -0
  124. voxelops-0.1.0/notebooks/05_full_pipeline.ipynb +555 -0
  125. voxelops-0.1.0/notebooks/README.md +99 -0
  126. voxelops-0.1.0/notebooks/generate_atlas_dataset.ipynb +239 -0
  127. voxelops-0.1.0/pyproject.toml +107 -0
  128. voxelops-0.1.0/src/voxelops/__init__.py +98 -0
  129. voxelops-0.1.0/src/voxelops/exceptions.py +158 -0
  130. voxelops-0.1.0/src/voxelops/runners/__init__.py +13 -0
  131. voxelops-0.1.0/src/voxelops/runners/_base.py +191 -0
  132. voxelops-0.1.0/src/voxelops/runners/heudiconv.py +202 -0
  133. voxelops-0.1.0/src/voxelops/runners/qsiparc.py +150 -0
  134. voxelops-0.1.0/src/voxelops/runners/qsiprep.py +187 -0
  135. voxelops-0.1.0/src/voxelops/runners/qsirecon.py +173 -0
  136. voxelops-0.1.0/src/voxelops/schemas/__init__.py +41 -0
  137. voxelops-0.1.0/src/voxelops/schemas/heudiconv.py +121 -0
  138. voxelops-0.1.0/src/voxelops/schemas/qsiparc.py +107 -0
  139. voxelops-0.1.0/src/voxelops/schemas/qsiprep.py +140 -0
  140. voxelops-0.1.0/src/voxelops/schemas/qsirecon.py +154 -0
  141. voxelops-0.1.0/src/voxelops/utils/__init__.py +1 -0
  142. voxelops-0.1.0/src/voxelops/utils/bids.py +486 -0
  143. voxelops-0.1.0/tests/conftest.py +144 -0
  144. voxelops-0.1.0/tests/test_exceptions.py +143 -0
  145. voxelops-0.1.0/tests/test_init.py +40 -0
  146. voxelops-0.1.0/tests/test_runners_base.py +177 -0
  147. voxelops-0.1.0/tests/test_runners_heudiconv.py +277 -0
  148. voxelops-0.1.0/tests/test_runners_qsiparc.py +189 -0
  149. voxelops-0.1.0/tests/test_runners_qsiprep.py +187 -0
  150. voxelops-0.1.0/tests/test_runners_qsirecon.py +223 -0
  151. voxelops-0.1.0/tests/test_schemas_heudiconv.py +80 -0
  152. voxelops-0.1.0/tests/test_schemas_qsiparc.py +73 -0
  153. voxelops-0.1.0/tests/test_schemas_qsiprep.py +88 -0
  154. voxelops-0.1.0/tests/test_schemas_qsirecon.py +115 -0
  155. voxelops-0.1.0/tests/test_utils_bids.py +449 -0
  156. voxelops-0.1.0/uv.lock +3965 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(tree:*)",
5
+ "Bash(uv lock:*)",
6
+ "Bash(make:*)",
7
+ "Bash(python -m py_compile:*)",
8
+ "Bash(python:*)",
9
+ "Bash(git add:*)",
10
+ "Bash(git commit:*)",
11
+ "Bash(git push)",
12
+ "Bash(git push:*)",
13
+ "Bash(.venv/bin/pip list:*)",
14
+ "Bash(git pull:*)",
15
+ "Bash(git stash:*)"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v3
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v4
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install ".[dev]"
28
+
29
+ - name: Lint with ruff
30
+ run: |
31
+ pip install ruff
32
+ ruff check .
33
+
34
+ - name: Test with pytest
35
+ run: |
36
+ pytest
37
+
38
+ - name: Upload coverage reports to Codecov
39
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de
40
+ with:
41
+ token: ${{ secrets.CODECOV_TOKEN }}
42
+ slug: GalKepler/VoxelOps
43
+
44
+ - name: Build documentation
45
+ run: |
46
+ pip install ".[docs]"
47
+ make -C docs html
@@ -0,0 +1,56 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Testing
24
+ .pytest_cache/
25
+ .coverage
26
+ htmlcov/
27
+ .tox/
28
+
29
+ # Virtual environments
30
+ venv/
31
+ env/
32
+ ENV/
33
+ .venv
34
+
35
+ # IDE
36
+ .vscode/
37
+ .idea/
38
+ *.swp
39
+ *.swo
40
+ *~
41
+
42
+ # OS
43
+ .DS_Store
44
+ Thumbs.db
45
+
46
+ # Logs
47
+ *.log
48
+ logs/
49
+
50
+ # Development
51
+ .mypy_cache/
52
+ .ruff_cache/
53
+
54
+ heuristic.py
55
+ bids_filters.json
56
+ qsirecon_spec.yaml
@@ -0,0 +1,18 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ - repo: https://github.com/psf/black
10
+ rev: 24.3.0
11
+ hooks:
12
+ - id: black
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.3.4
15
+ hooks:
16
+ - id: ruff
17
+ args: [--fix, --exit-non-zero-on-fix]
18
+ - id: ruff-format
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,20 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.12"
7
+
8
+ sphinx:
9
+ configuration: docs/conf.py
10
+
11
+ python:
12
+ install:
13
+ - requirements: docs/requirements.txt
14
+ - method: pip
15
+ path: .
16
+
17
+ formats:
18
+ - htmlzip
19
+ - pdf
20
+ - epub
@@ -0,0 +1,13 @@
1
+ # uv ignore file - exclude directories from uv operations
2
+ .git/
3
+ .venv/
4
+ __pycache__/
5
+ *.pyc
6
+ .pytest_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+ .vscode/
13
+ .idea/
@@ -0,0 +1,217 @@
1
+ # VoxelOps Architecture
2
+
3
+ ## Design Principles
4
+
5
+ 1. **Simplicity** -- Functions, not classes. Dicts, not complex objects.
6
+ 2. **Clarity** -- Typed dataclass schemas for every input, output, and configuration.
7
+ 3. **Reproducibility** -- The exact Docker command is stored in every execution record.
8
+ 4. **Database-Friendly** -- Results are plain dicts, trivial to persist anywhere.
9
+ 5. **Extensibility** -- New procedures follow the same pattern, easy to add.
10
+
11
+ ## Directory Structure
12
+
13
+ ```
14
+ VoxelOps/
15
+ src/voxelops/
16
+ __init__.py # Package exports, version
17
+ exceptions.py # Exception hierarchy
18
+ runners/
19
+ __init__.py # Runner exports
20
+ _base.py # Shared: run_docker, validate_input_dir, validate_participant
21
+ heudiconv.py # DICOM -> BIDS
22
+ qsiprep.py # Diffusion preprocessing
23
+ qsirecon.py # Diffusion reconstruction
24
+ qsiparc.py # Parcellation (via parcellate, not Docker)
25
+ schemas/
26
+ __init__.py # Schema exports
27
+ heudiconv.py # HeudiconvInputs / Outputs / Defaults
28
+ qsiprep.py # QSIPrepInputs / Outputs / Defaults
29
+ qsirecon.py # QSIReconInputs / Outputs / Defaults
30
+ qsiparc.py # QSIParcInputs / Outputs / Defaults
31
+ utils/
32
+ __init__.py
33
+ bids.py # BIDS post-processing (IntendedFor, fmap cleanup)
34
+ examples/
35
+ dicom_to_bids.py # HeudiConv example
36
+ qsiprep.py # QSIPrep example
37
+ qsirecon.py # QSIRecon example
38
+ qsiparc.py # QSIParc example
39
+ notebooks/
40
+ 01_heudiconv_basics.ipynb # Step-by-step tutorials
41
+ 02_qsiprep_basics.ipynb
42
+ 03_qsirecon_basics.ipynb
43
+ 04_qsiparc_basics.ipynb
44
+ 05_full_pipeline.ipynb
45
+ tests/ # Pytest test suite
46
+ docs/ # Sphinx documentation source
47
+ pyproject.toml # Build config, dependencies
48
+ ```
49
+
50
+ ## Code Organization
51
+
52
+ ### Runners (`runners/`)
53
+
54
+ Each runner is a function that:
55
+
56
+ 1. Accepts an `Inputs` dataclass and optional `Defaults` config
57
+ 2. Validates inputs (directory exists, participant found)
58
+ 3. Builds a Docker command (or calls `parcellate` directly for QSIParc)
59
+ 4. Executes via `run_docker()` (or `run_parcellations()`)
60
+ 5. Returns an execution record dict with expected outputs
61
+
62
+ ```python
63
+ def run_procedure(
64
+ inputs: ProcedureInputs,
65
+ config: Optional[ProcedureDefaults] = None,
66
+ **overrides,
67
+ ) -> Dict[str, Any]:
68
+ config = config or ProcedureDefaults()
69
+
70
+ for key, value in overrides.items():
71
+ if hasattr(config, key):
72
+ setattr(config, key, value)
73
+
74
+ validate_input_dir(inputs.input_dir)
75
+ validate_participant(inputs.input_dir, inputs.participant)
76
+
77
+ output_dir = inputs.output_dir or default_path
78
+ expected_outputs = ProcedureOutputs.from_inputs(inputs, output_dir)
79
+
80
+ cmd = ["docker", "run", "--rm", ...]
81
+ result = run_docker(cmd, "tool_name", inputs.participant, log_dir)
82
+
83
+ result["inputs"] = inputs
84
+ result["config"] = config
85
+ result["expected_outputs"] = expected_outputs
86
+ return result
87
+ ```
88
+
89
+ ### Schemas (`schemas/`)
90
+
91
+ Each procedure defines three dataclasses:
92
+
93
+ **Inputs** -- required parameters with `__post_init__` path coercion:
94
+
95
+ ```python
96
+ @dataclass
97
+ class ProcedureInputs:
98
+ input_dir: Path
99
+ participant: str
100
+ output_dir: Optional[Path] = None
101
+
102
+ def __post_init__(self):
103
+ self.input_dir = Path(self.input_dir)
104
+ if self.output_dir:
105
+ self.output_dir = Path(self.output_dir)
106
+ ```
107
+
108
+ **Outputs** -- generated from inputs via `from_inputs()` classmethod:
109
+
110
+ ```python
111
+ @dataclass
112
+ class ProcedureOutputs:
113
+ output_dir: Path
114
+ participant_dir: Path
115
+
116
+ @classmethod
117
+ def from_inputs(cls, inputs, output_dir):
118
+ return cls(
119
+ output_dir=output_dir,
120
+ participant_dir=output_dir / f"sub-{inputs.participant}",
121
+ )
122
+ ```
123
+
124
+ **Defaults** -- configuration with brain bank standard values:
125
+
126
+ ```python
127
+ @dataclass
128
+ class ProcedureDefaults:
129
+ nprocs: int = 8
130
+ mem_mb: int = 16000
131
+ docker_image: str = "tool/image:latest"
132
+ ```
133
+
134
+ ### Base Utilities (`runners/_base.py`)
135
+
136
+ Three shared functions:
137
+
138
+ - `validate_input_dir(path, dir_type)` -- raises `InputValidationError` if missing or not a directory
139
+ - `validate_participant(input_dir, participant, prefix)` -- raises `InputValidationError` if participant subdir not found
140
+ - `run_docker(cmd, tool_name, participant, log_dir, capture_output)` -- executes subprocess, builds execution record, writes JSON log, raises `ProcedureExecutionError` on failure
141
+
142
+ ### BIDS Utilities (`utils/bids.py`)
143
+
144
+ Post-processing for HeudiConv output:
145
+
146
+ - `post_process_heudiconv_output()` -- orchestrates three steps after DICOM conversion
147
+ - `verify_fmap_epi_files()` -- checks fieldmap NIfTI + JSON exist
148
+ - `add_intended_for_to_fmaps()` -- writes `IntendedFor` into fmap JSON sidecars
149
+ - `remove_bval_bvec_from_fmaps()` -- hides spurious `.bvec`/`.bval` files from fmap dirs
150
+
151
+ ### Exceptions (`exceptions.py`)
152
+
153
+ ```
154
+ YALabProcedureError (base, aliased as ProcedureError)
155
+ ProcedureExecutionError
156
+ DockerExecutionError
157
+ ProcedureConfigurationError
158
+ FreeSurferLicenseError
159
+ InputValidationError
160
+ OutputCollectionError
161
+ BIDSValidationError
162
+ DependencyError
163
+ ```
164
+
165
+ ## Execution Flow
166
+
167
+ ```
168
+ User calls run_procedure(inputs, config)
169
+ -> Validate inputs
170
+ -> Generate expected outputs
171
+ -> Build Docker command
172
+ -> run_docker() executes subprocess
173
+ -> Build execution record dict
174
+ -> Attach inputs, config, expected_outputs
175
+ -> Return dict
176
+ ```
177
+
178
+ ## Execution Record Structure
179
+
180
+ ```python
181
+ {
182
+ "tool": str,
183
+ "participant": str,
184
+ "command": List[str],
185
+ "exit_code": int,
186
+ "start_time": str, # ISO format
187
+ "end_time": str,
188
+ "duration_seconds": float,
189
+ "duration_human": str,
190
+ "success": bool,
191
+ "log_file": str, # Path to JSON log (if log_dir provided)
192
+ "stdout": str, # If capture_output=True
193
+ "stderr": str,
194
+ "error": str, # If failed
195
+ "inputs": ProcedureInputs,
196
+ "config": ProcedureDefaults,
197
+ "expected_outputs": ProcedureOutputs,
198
+ }
199
+ ```
200
+
201
+ ## Adding a New Procedure
202
+
203
+ 1. **Schema** -- create `schemas/your_procedure.py` with `Inputs`, `Outputs`, `Defaults`
204
+ 2. **Runner** -- create `runners/your_procedure.py` with `run_your_procedure()`
205
+ 3. **Exports** -- add to `schemas/__init__.py`, `runners/__init__.py`, and `__init__.py`
206
+ 4. **Tests** -- create `tests/test_runners_your_procedure.py` and `tests/test_schemas_your_procedure.py`
207
+ 5. **Example** -- add `examples/your_procedure.py`
208
+
209
+ ## Key Design Decisions
210
+
211
+ | Decision | Rationale |
212
+ |----------|-----------|
213
+ | Dataclasses for schemas | Type hints for IDE support, automatic `__init__` and `__repr__` |
214
+ | Functions, not classes | Simpler, no state, no inheritance, easier to test |
215
+ | Command stored in record | Perfect reproducibility -- just rerun the command |
216
+ | Minimal dependencies | Faster installs, fewer breaking changes, no Nipype lock-in |
217
+ | No output validation | Docker container handles that; keep this package simple |
voxelops-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YALab DevOps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ .PHONY: help install install-dev install-all test test-cov format lint clean lock upgrade venv
2
+
3
+ help: ## Show this help message
4
+ @echo "Usage: make [target]"
5
+ @echo ""
6
+ @echo "Targets:"
7
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
8
+
9
+ venv: ## Create virtual environment
10
+ uv venv
11
+ @echo ""
12
+ @echo "Virtual environment created. Activate with:"
13
+ @echo " source .venv/bin/activate # macOS/Linux"
14
+ @echo " .venv\\Scripts\\activate # Windows"
15
+
16
+ install: ## Install package in editable mode
17
+ uv pip install -e .
18
+
19
+ install-dev: ## Install package with dev dependencies
20
+ uv pip install -e ".[dev]"
21
+
22
+ install-all: ## Install package with all optional dependencies
23
+ uv pip install -e ".[dev,notebooks,config]"
24
+
25
+ test: ## Run tests
26
+ pytest
27
+
28
+ test-cov: ## Run tests with coverage report
29
+ pytest --cov=yalab_procedures --cov-report=term-missing --cov-report=html
30
+ @echo ""
31
+ @echo "Coverage report generated in htmlcov/index.html"
32
+
33
+ format: ## Format code with black
34
+ black src tests examples
35
+
36
+ format-check: ## Check code formatting without changes
37
+ black --check src tests examples
38
+
39
+ lint: ## Lint code with ruff
40
+ ruff check src tests examples
41
+
42
+ lint-fix: ## Lint and auto-fix issues
43
+ ruff check src tests examples --fix
44
+
45
+ qa: format-check lint test ## Run all quality checks
46
+
47
+ lock: ## Update lock file
48
+ uv lock
49
+
50
+ upgrade: ## Upgrade all dependencies
51
+ uv lock --upgrade
52
+ @echo ""
53
+ @echo "Dependencies upgraded. Run 'make install-dev' to install updates."
54
+
55
+ sync: ## Sync environment with lock file
56
+ uv pip sync
57
+
58
+ clean: ## Remove build artifacts and cache files
59
+ rm -rf build/
60
+ rm -rf dist/
61
+ rm -rf *.egg-info
62
+ rm -rf .pytest_cache
63
+ rm -rf .coverage
64
+ rm -rf htmlcov/
65
+ find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
66
+ find . -type f -name "*.pyc" -delete
67
+
68
+ clean-venv: ## Remove virtual environment
69
+ rm -rf .venv
70
+
71
+ notebook: ## Start Jupyter notebook server
72
+ jupyter notebook notebooks/
73
+
74
+ lab: ## Start Jupyter lab server
75
+ jupyter lab notebooks/
76
+
77
+ docs: ## Open main documentation files
78
+ @echo "Documentation files:"
79
+ @echo " README.md - Main documentation"
80
+ @echo " DEVELOPMENT.md - Development guide"
81
+ @echo " UV_QUICKSTART.md - Quick start with uv"
82
+ @echo " ARCHITECTURE.md - Architecture documentation"
83
+ @echo " notebooks/README.md - Notebook examples"
84
+
85
+ setup: venv install-dev ## Complete setup: create venv and install dev dependencies
86
+ @echo ""
87
+ @echo "Setup complete! Don't forget to activate the virtual environment:"
88
+ @echo " source .venv/bin/activate"
89
+
90
+ # Development workflow targets
91
+ dev-start: ## Start development session (activate venv, show status)
92
+ @echo "Development environment ready!"
93
+ @echo ""
94
+ @echo "Quick commands:"
95
+ @echo " make test - Run tests"
96
+ @echo " make format - Format code"
97
+ @echo " make lint - Lint code"
98
+ @echo " make qa - Run all quality checks"
99
+ @echo ""
100
+
101
+ # CI targets
102
+ ci: format-check lint test ## Run CI checks (format, lint, test)
103
+ @echo ""
104
+ @echo "✅ All CI checks passed!"
105
+
106
+ # Build targets
107
+ build: ## Build distribution packages
108
+ uv build
109
+
110
+ publish-test: ## Publish to Test PyPI
111
+ uv publish --publish-url https://test.pypi.org/legacy/
112
+
113
+ publish: ## Publish to PyPI
114
+ uv publish
115
+
116
+ # Info targets
117
+ info: ## Show package and environment info
118
+ @echo "Package: yalab-procedures"
119
+ @echo "Version: $$(grep '^version = ' pyproject.toml | cut -d'"' -f2)"
120
+ @echo ""
121
+ @echo "Python: $$(python --version 2>&1)"
122
+ @echo "uv: $$(uv --version 2>&1)"
123
+ @echo ""
124
+ @echo "Virtual environment:"
125
+ @if [ -d .venv ]; then \
126
+ echo " Status: ✅ Exists"; \
127
+ echo " Path: $$(pwd)/.venv"; \
128
+ else \
129
+ echo " Status: ❌ Not created (run 'make venv')"; \
130
+ fi