voxelops 0.1.0__py3-none-any.whl

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.
voxelops/utils/bids.py ADDED
@@ -0,0 +1,486 @@
1
+ """BIDS post-processing utilities for HeudiConv output."""
2
+
3
+ import json
4
+ import stat
5
+ from pathlib import Path
6
+ from typing import Any, Callable, Dict, List, Optional
7
+
8
+
9
+ def _run_post_processing_step(
10
+ step_func: Callable,
11
+ step_name: str,
12
+ results: Dict[str, Any],
13
+ *args,
14
+ **kwargs,
15
+ ) -> None:
16
+ """Helper to run a post-processing step and record its results."""
17
+ try:
18
+ step_result = step_func(*args, **kwargs)
19
+ results[step_name] = step_result
20
+
21
+ if not step_result["success"]:
22
+ results["errors"].extend(step_result.get("errors", []))
23
+ results["success"] = False
24
+ except Exception as e:
25
+ results["errors"].append(f"{step_name.capitalize()} failed: {e}")
26
+ results["success"] = False
27
+
28
+
29
+ def post_process_heudiconv_output(
30
+ bids_dir: Path,
31
+ participant: str,
32
+ session: Optional[str] = None,
33
+ dry_run: bool = False,
34
+ ) -> Dict[str, Any]:
35
+ """
36
+ Post-process HeudiConv output to ensure BIDS compliance.
37
+
38
+ Orchestrates all post-processing steps:
39
+
40
+ 1. Verify fieldmap EPI files exist
41
+ 2. Add IntendedFor fields to fmap JSONs
42
+ 3. Hide bval/bvec from fmap directories (rename with dot prefix)
43
+
44
+ Parameters
45
+ ----------
46
+ bids_dir : Path
47
+ Root BIDS directory.
48
+ participant : str
49
+ Participant ID (without 'sub-' prefix).
50
+ session : Optional[str], optional
51
+ Session ID (without 'ses-' prefix), if applicable, by default None.
52
+ dry_run : bool, optional
53
+ If True, report changes without modifying files, by default False.
54
+
55
+ Returns
56
+ -------
57
+ Dict[str, Any]
58
+ A dictionary with results:
59
+
60
+ - 'success': bool
61
+ - 'verification': dict
62
+ - 'intended_for': dict
63
+ - 'cleanup': dict
64
+ - 'errors': list
65
+ """
66
+ results = {
67
+ "success": True,
68
+ "errors": [],
69
+ "verification": {},
70
+ "intended_for": {},
71
+ "cleanup": {},
72
+ }
73
+
74
+ # Build participant directory path
75
+ participant_dir = bids_dir / f"sub-{participant}"
76
+ if session:
77
+ participant_dir = participant_dir / f"ses-{session}"
78
+
79
+ if not participant_dir.exists():
80
+ results["success"] = False
81
+ results["errors"].append(f"Participant directory not found: {participant_dir}")
82
+ return results
83
+
84
+ # Step 1: Verify fieldmap EPI files exist
85
+ _run_post_processing_step(
86
+ verify_fmap_epi_files,
87
+ "verification",
88
+ results,
89
+ participant_dir,
90
+ session,
91
+ )
92
+
93
+ # Step 2: Add IntendedFor to fieldmap JSONs
94
+ _run_post_processing_step(
95
+ add_intended_for_to_fmaps,
96
+ "intended_for",
97
+ results,
98
+ participant_dir,
99
+ session,
100
+ dry_run,
101
+ )
102
+
103
+ # Step 3: Hide bval/bvec from fmap directories
104
+ _run_post_processing_step(
105
+ remove_bval_bvec_from_fmaps,
106
+ "cleanup",
107
+ results,
108
+ participant_dir,
109
+ session,
110
+ dry_run,
111
+ )
112
+
113
+ return results
114
+
115
+
116
+ def verify_fmap_epi_files(
117
+ participant_dir: Path,
118
+ session: Optional[str] = None,
119
+ ) -> Dict[str, Any]:
120
+ """
121
+ Verify that expected fieldmap EPI files exist.
122
+
123
+ Checks for existence of ``*acq-dwi*_epi.nii.gz`` and ``.json`` in ``fmap/`` directory.
124
+
125
+ Parameters
126
+ ----------
127
+ participant_dir : Path
128
+ Path to participant directory (or session directory if session exists).
129
+ session : Optional[str], optional
130
+ Session ID (for logging purposes), by default None.
131
+
132
+ Returns
133
+ -------
134
+ Dict[str, Any]
135
+ Dictionary with verification results.
136
+ """
137
+ results = {
138
+ "success": True,
139
+ "found_files": [],
140
+ "missing_files": [],
141
+ "errors": [],
142
+ }
143
+
144
+ fmap_dir = participant_dir / "fmap"
145
+
146
+ if not fmap_dir.exists():
147
+ results["success"] = False
148
+ results["errors"].append(f"Fieldmap directory not found: {fmap_dir}")
149
+ return results
150
+
151
+ # Look for DWI fieldmap files
152
+ dwi_epi_nii = list(fmap_dir.glob("*acq-dwi*_epi.nii.gz"))
153
+ dwi_epi_json = list(fmap_dir.glob("*acq-dwi*_epi.json"))
154
+
155
+ if dwi_epi_nii:
156
+ results["found_files"].extend([str(f.name) for f in dwi_epi_nii])
157
+ else:
158
+ results["missing_files"].append("*acq-dwi*_epi.nii.gz")
159
+ results["errors"].append("No DWI fieldmap NIfTI files found")
160
+ results["success"] = False
161
+
162
+ if dwi_epi_json:
163
+ results["found_files"].extend([str(f.name) for f in dwi_epi_json])
164
+ else:
165
+ results["missing_files"].append("*acq-dwi*_epi.json")
166
+ results["errors"].append("No DWI fieldmap JSON files found")
167
+ results["success"] = False
168
+
169
+ return results
170
+
171
+
172
+ def _process_single_fmap_json(
173
+ fmap_json: Path,
174
+ participant_dir: Path,
175
+ session: Optional[str],
176
+ dry_run: bool,
177
+ results: Dict[str, Any],
178
+ ) -> None:
179
+ """Processes a single fmap JSON file to add IntendedFor field."""
180
+ try:
181
+ # Determine acquisition type from filename
182
+ filename = fmap_json.name
183
+
184
+ if "acq-dwi" in filename:
185
+ # DWI fieldmap -> find DWI targets
186
+ target_files = _find_dwi_targets(participant_dir)
187
+ acq_type = "DWI"
188
+ elif "acq-func" in filename:
189
+ # Functional fieldmap -> find all BOLD targets
190
+ target_files = _find_func_targets(participant_dir)
191
+ acq_type = "functional"
192
+ else:
193
+ results["errors"].append(f"Unknown acquisition type in {filename}")
194
+ return
195
+
196
+ if not target_files:
197
+ results["errors"].append(f"No target files found for {filename}")
198
+ return
199
+
200
+ # Build IntendedFor paths (relative to session or participant directory)
201
+ intended_for_paths = [
202
+ _build_intended_for_path(target, participant_dir, session)
203
+ for target in target_files
204
+ ]
205
+
206
+ # Update JSON file
207
+ if not dry_run:
208
+ success = _update_json_sidecar(fmap_json, intended_for_paths)
209
+ if success:
210
+ results["updated_files"].append(
211
+ {
212
+ "file": str(fmap_json.name),
213
+ "type": acq_type,
214
+ "targets": intended_for_paths,
215
+ }
216
+ )
217
+ else:
218
+ results["errors"].append(f"Failed to update {filename}")
219
+ else:
220
+ results["updated_files"].append(
221
+ {
222
+ "file": str(fmap_json.name),
223
+ "type": acq_type,
224
+ "targets": intended_for_paths,
225
+ "note": "Dry run - not modified",
226
+ }
227
+ )
228
+
229
+ except Exception as e:
230
+ results["errors"].append(f"Error processing {fmap_json.name}: {e}")
231
+ results["success"] = False
232
+
233
+
234
+ def add_intended_for_to_fmaps(
235
+ participant_dir: Path,
236
+ session: Optional[str] = None,
237
+ dry_run: bool = False,
238
+ ) -> Dict[str, Any]:
239
+ """
240
+ Add IntendedFor fields to fieldmap JSON files.
241
+
242
+ Maps fieldmaps to target files based on acquisition type:
243
+
244
+ - ``acq-dwi*_epi.json`` -> all ``dwi/*_dwi.nii.gz`` files
245
+ - ``acq-func*_epi.json`` -> all ``func/*_bold.nii.gz`` files
246
+
247
+ Parameters
248
+ ----------
249
+ participant_dir : Path
250
+ Path to participant directory (or session directory if session exists).
251
+ session : Optional[str], optional
252
+ Session ID (for building relative paths), by default None.
253
+ dry_run : bool, optional
254
+ If True, report changes without modifying files, by default False.
255
+
256
+ Returns
257
+ -------
258
+ Dict[str, Any]
259
+ Dictionary with processing results.
260
+ """
261
+ results = {
262
+ "success": True,
263
+ "updated_files": [],
264
+ "errors": [],
265
+ "dry_run": dry_run,
266
+ }
267
+
268
+ fmap_dir = participant_dir / "fmap"
269
+
270
+ if not fmap_dir.exists():
271
+ results["success"] = False
272
+ results["errors"].append(f"Fieldmap directory not found: {fmap_dir}")
273
+ return results
274
+
275
+ # Find all fieldmap JSON files
276
+ fmap_jsons = list(fmap_dir.glob("*_epi.json"))
277
+
278
+ if not fmap_jsons:
279
+ results["errors"].append("No fieldmap JSON files found")
280
+ results["success"] = False
281
+ return results
282
+
283
+ for fmap_json in fmap_jsons:
284
+ _process_single_fmap_json(fmap_json, participant_dir, session, dry_run, results)
285
+
286
+ return results
287
+
288
+
289
+ def remove_bval_bvec_from_fmaps(
290
+ participant_dir: Path,
291
+ session: Optional[str] = None,
292
+ dry_run: bool = False,
293
+ ) -> Dict[str, Any]:
294
+ """
295
+ Hide .bvec and .bval files from fmap directories by renaming with dot prefix.
296
+
297
+ These files are incorrectly generated by dcm2niix for fieldmaps
298
+ and are not BIDS-compliant for EPI fieldmaps. Instead of deleting,
299
+ we rename them with a leading dot to hide them (e.g., ``.filename.bvec``).
300
+
301
+ Parameters
302
+ ----------
303
+ participant_dir : Path
304
+ Path to participant directory (or session directory if session exists).
305
+ session : Optional[str], optional
306
+ Session ID (for logging purposes), by default None.
307
+ dry_run : bool, optional
308
+ If True, report files to hide without renaming, by default False.
309
+
310
+ Returns
311
+ -------
312
+ Dict[str, Any]
313
+ Dictionary with cleanup results.
314
+ """
315
+ results = {
316
+ "success": True,
317
+ "hidden_files": [],
318
+ "errors": [],
319
+ "dry_run": dry_run,
320
+ }
321
+
322
+ fmap_dir = participant_dir / "fmap"
323
+
324
+ if not fmap_dir.exists():
325
+ results["errors"].append(f"Fieldmap directory not found: {fmap_dir}")
326
+ results["success"] = False
327
+ return results
328
+
329
+ # Find all .bvec and .bval files in fmap directory (excluding already hidden ones)
330
+ bvec_files = [f for f in fmap_dir.glob("*_epi.bvec") if not f.name.startswith(".")]
331
+ bval_files = [f for f in fmap_dir.glob("*_epi.bval") if not f.name.startswith(".")]
332
+
333
+ files_to_hide = bvec_files + bval_files
334
+
335
+ if not files_to_hide:
336
+ # Not an error - just means files are already clean/hidden
337
+ return results
338
+
339
+ for file_path in files_to_hide:
340
+ try:
341
+ if not dry_run:
342
+ # Rename with leading dot to hide
343
+ hidden_path = file_path.parent / f".{file_path.name}"
344
+ file_path.rename(hidden_path)
345
+ results["hidden_files"].append(
346
+ {
347
+ "original": str(file_path.name),
348
+ "hidden_as": str(hidden_path.name),
349
+ }
350
+ )
351
+ else:
352
+ results["hidden_files"].append(
353
+ {
354
+ "file": str(file_path.name),
355
+ "will_hide_as": f".{file_path.name}",
356
+ "note": "Dry run - not renamed",
357
+ }
358
+ )
359
+ except Exception as e:
360
+ results["errors"].append(f"Failed to hide {file_path.name}: {e}")
361
+ results["success"] = False
362
+
363
+ return results
364
+
365
+
366
+ # Private helper functions
367
+
368
+
369
+ def _find_dwi_targets(participant_dir: Path) -> List[Path]:
370
+ """Find all DWI NIfTI files in dwi directory."""
371
+ dwi_dir = participant_dir / "dwi"
372
+ if not dwi_dir.exists():
373
+ return []
374
+ return list(dwi_dir.glob("*_dwi.nii.gz"))
375
+
376
+
377
+ def _find_func_targets(participant_dir: Path) -> List[Path]:
378
+ """Find all functional BOLD NIfTI files in func directory."""
379
+ func_dir = participant_dir / "func"
380
+ if not func_dir.exists():
381
+ return []
382
+ return list(func_dir.glob("*_bold.nii.gz"))
383
+
384
+
385
+ def _build_intended_for_path(
386
+ target_file: Path,
387
+ participant_dir: Path,
388
+ session: Optional[str] = None,
389
+ ) -> str:
390
+ """
391
+ Build BIDS-compliant relative path for IntendedFor field.
392
+
393
+ Paths are relative to the session directory (if session exists)
394
+ or participant directory.
395
+
396
+ Parameters
397
+ ----------
398
+ target_file : Path
399
+ Absolute path to target file.
400
+ participant_dir : Path
401
+ Path to participant/session directory.
402
+ session : Optional[str], optional
403
+ Session ID if applicable, by default None.
404
+
405
+ Returns
406
+ -------
407
+ str
408
+ Relative path string for IntendedFor field.
409
+ """
410
+ # Get path relative to participant_dir
411
+ try:
412
+ rel_path = target_file.relative_to(participant_dir)
413
+ if session: # Add "ses-{session}" before
414
+ rel_path = f"ses-{session}/{rel_path}"
415
+ return str(rel_path)
416
+ except ValueError:
417
+ # If relative_to fails, build manually
418
+ # This shouldn't happen if paths are constructed correctly
419
+ return str(target_file.name)
420
+
421
+
422
+ def _update_json_sidecar(json_path: Path, intended_for: List[str]) -> bool:
423
+ """
424
+ Update JSON sidecar file with IntendedFor field.
425
+
426
+ Reads existing JSON, adds/updates IntendedFor field, and writes back.
427
+ Preserves all existing fields. Handles read-only files by making them writable.
428
+
429
+ Parameters
430
+ ----------
431
+ json_path : Path
432
+ Path to JSON file.
433
+ intended_for : List[str]
434
+ List of relative paths for IntendedFor field.
435
+
436
+ Returns
437
+ -------
438
+ bool
439
+ True if successful, False otherwise.
440
+ """
441
+ try:
442
+ # Read existing JSON
443
+ data = _read_json_sidecar(json_path)
444
+ if data is None:
445
+ return False
446
+
447
+ # Add IntendedFor field (BIDS spec requires array)
448
+ data["IntendedFor"] = intended_for
449
+
450
+ # Make file writable if it's read-only (HeudiConv creates read-only files)
451
+ current_mode = json_path.stat().st_mode
452
+ if not (current_mode & stat.S_IWUSR):
453
+ # Add user write permission
454
+ json_path.chmod(current_mode | stat.S_IWUSR)
455
+
456
+ # Write back with formatting
457
+ with open(json_path, "w") as f:
458
+ json.dump(data, f, indent=2)
459
+
460
+ return True
461
+
462
+ except Exception as e:
463
+ print(f"Error updating {json_path}: {e}")
464
+ return False
465
+
466
+
467
+ def _read_json_sidecar(json_path: Path) -> Optional[Dict[str, Any]]:
468
+ """
469
+ Read JSON sidecar file with error handling.
470
+
471
+ Parameters
472
+ ----------
473
+ json_path : Path
474
+ Path to JSON file.
475
+
476
+ Returns
477
+ -------
478
+ Optional[Dict[str, Any]]
479
+ Dictionary with JSON contents, or None if reading fails.
480
+ """
481
+ try:
482
+ with open(json_path) as f:
483
+ return json.load(f)
484
+ except Exception as e:
485
+ print(f"Error reading {json_path}: {e}")
486
+ return None
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: voxelops
3
+ Version: 0.1.0
4
+ Summary: Clean, simple neuroimaging pipeline automation for brain banks
5
+ Project-URL: Homepage, https://github.com/yalab-devops/VoxelOps
6
+ Project-URL: Documentation, https://github.com/yalab-devops/VoxelOps#readme
7
+ Project-URL: Repository, https://github.com/yalab-devops/VoxelOps
8
+ Project-URL: Issues, https://github.com/yalab-devops/VoxelOps/issues
9
+ Author-email: YALab DevOps <yalab.dev@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: brain-bank,docker,heudiconv,neuroimaging,qsiprep,qsirecon
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: ipython>=8.12.3
24
+ Requires-Dist: pandas>=2.0.3
25
+ Requires-Dist: pyyaml>=6.0.3
26
+ Requires-Dist: templateflow>=24.2.2
27
+ Provides-Extra: config
28
+ Requires-Dist: pyyaml>=6.0; extra == 'config'
29
+ Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'config'
30
+ Provides-Extra: dev
31
+ Requires-Dist: black>=23.0; extra == 'dev'
32
+ Requires-Dist: pre-commit>=3.0; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
34
+ Requires-Dist: pytest>=7.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
36
+ Provides-Extra: docs
37
+ Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
38
+ Requires-Dist: sphinx>=7.0; extra == 'docs'
39
+ Provides-Extra: notebooks
40
+ Requires-Dist: jupyter>=1.0; extra == 'notebooks'
41
+ Requires-Dist: matplotlib>=3.5; extra == 'notebooks'
42
+ Requires-Dist: pandas>=1.3; extra == 'notebooks'
43
+ Requires-Dist: seaborn>=0.11; extra == 'notebooks'
44
+ Description-Content-Type: text/x-rst
45
+
46
+ VoxelOps
47
+ ========
48
+
49
+ .. image:: https://github.com/GalKepler/VoxelOps/blob/main/docs/images/Gemini_Generated_Image_m9bi47m9bi47m9bi.png?raw=true
50
+ :alt: VoxelOps Logo
51
+
52
+ Clean, simple neuroimaging pipeline automation for brain banks.
53
+ ---------------------------------------------------------------
54
+
55
+ Brain banks need to process neuroimaging data **consistently**, **reproducibly**, and **auditably**. VoxelOps makes that simple by wrapping Docker-based neuroimaging tools into clean Python functions that return plain dicts -- ready for your database, your logs, and your peace of mind.
56
+
57
+ ========
58
+ Overview
59
+ ========
60
+
61
+ .. list-table::
62
+ :stub-columns: 1
63
+
64
+ * - docs
65
+ - |docs|
66
+ * - tests, CI & coverage
67
+ - |github-actions| |codecov| |codacy|
68
+ * - version
69
+ - |pypi| |python|
70
+ * - styling
71
+ - |black| |isort| |flake8| |pre-commit|
72
+ * - license
73
+ - |license|
74
+
75
+ .. |docs| image:: https://readthedocs.org/projects/voxelops/badge/?version=latest
76
+ :target: https://voxelops.readthedocs.io/en/latest/?badge=latest
77
+ :alt: Documentation Status
78
+
79
+ .. |github-actions| image:: https://github.com/GalKepler/VoxelOps/actions/workflows/ci.yml/badge.svg
80
+ :target: https://github.com/GalKepler/VoxelOps/actions/workflows/ci.yml
81
+ :alt: CI
82
+
83
+ .. |codecov| image:: https://codecov.io/gh/GalKepler/VoxelOps/graph/badge.svg?token=GBOLQOB5VI
84
+ :target: https://codecov.io/gh/GalKepler/VoxelOps
85
+ :alt: codecov
86
+
87
+ .. |codacy| image:: https://app.codacy.com/project/badge/Grade/84bfb76385244fc3b80bc18e5c8f3bfd
88
+ :target: https://app.codacy.com/gh/GalKepler/VoxelOps/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade
89
+ :alt: Codacy Badge
90
+
91
+ .. |pypi| image:: https://badge.fury.io/py/voxelops.svg
92
+ :target: https://badge.fury.io/py/voxelops
93
+ :alt: PyPI version
94
+
95
+ .. |python| image:: https://img.shields.io/badge/python-3.10%2B-blue.svg
96
+ :target: https://www.python.org/downloads/
97
+ :alt: Python 3.10+
98
+
99
+ .. |license| image:: https://img.shields.io/github/license/yalab-devops/yalab-procedures.svg
100
+ :target: https://opensource.org/license/mit
101
+ :alt: License
102
+
103
+ .. |black| image:: https://img.shields.io/badge/formatter-black-000000.svg
104
+ :target: https://github.com/psf/black
105
+
106
+ .. |isort| image:: https://img.shields.io/badge/imports-isort-%231674b1.svg
107
+ :target: https://pycqa.github.io/isort/
108
+
109
+ .. |flake8| image:: https://img.shields.io/badge/style-flake8-000000.svg
110
+ :target: https://flake8.pycqa.org/en/latest/
111
+
112
+ .. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
113
+ :target: https://github.com/pre-commit/pre-commit
114
+
115
+
116
+
117
+
118
+ Features
119
+ --------
120
+
121
+ - **Simple Functions** -- No classes, no inheritance -- just ``run_*()`` functions that return dicts
122
+ - **Clear Schemas** -- Typed dataclass inputs, outputs, and defaults for every procedure
123
+ - **Reproducibility** -- The exact Docker command is stored in every execution record
124
+ - **Database-Ready** -- Results are plain dicts, trivial to save to PostgreSQL, MongoDB, or JSON
125
+ - **Brain Bank Defaults** -- Define your standard parameters once, reuse across all participants
126
+ - **Comprehensive Logging** -- Every run logged to JSON with timestamps, duration, and exit codes
127
+
128
+ Installation
129
+ ------------
130
+
131
+ .. code-block:: bash
132
+
133
+ pip install voxelops
134
+
135
+ For development:
136
+
137
+ .. code-block:: bash
138
+
139
+ git clone https://github.com/yalab-devops/VoxelOps.git
140
+ cd VoxelOps
141
+ pip install -e ".[dev]"
142
+
143
+ **Requirements**: Python >= 3.8, Docker installed and accessible.
144
+
145
+ Quick Start
146
+ -----------
147
+
148
+ .. code-block:: python
149
+
150
+ from voxelops import run_qsiprep, QSIPrepInputs
151
+
152
+ inputs = QSIPrepInputs(
153
+ bids_dir="/data/bids",
154
+ participant="01",
155
+ )
156
+
157
+ result = run_qsiprep(inputs, nprocs=16)
158
+
159
+ print(f"Completed in: {result['duration_human']}")
160
+ print(f"Outputs: {result['expected_outputs'].qsiprep_dir}")
161
+ print(f"Command: {' '.join(result['command'])}")
162
+
163
+ Available Procedures
164
+ --------------------
165
+
166
+ .. list-table::
167
+ :header-rows: 1
168
+ :widths: 15 35 25 25
169
+
170
+ * - Procedure
171
+ - Purpose
172
+ - Function
173
+ - Execution
174
+ * - HeudiConv
175
+ - DICOM to BIDS conversion
176
+ - ``run_heudiconv()``
177
+ - Docker
178
+ * - QSIPrep
179
+ - Diffusion MRI preprocessing
180
+ - ``run_qsiprep()``
181
+ - Docker
182
+ * - QSIRecon
183
+ - Diffusion reconstruction & connectivity
184
+ - ``run_qsirecon()``
185
+ - Docker
186
+ * - QSIParc
187
+ - Parcellation via ``parcellate``
188
+ - ``run_qsiparc()``
189
+ - Python (direct)
190
+
191
+ Brain Bank Standards
192
+ --------------------
193
+
194
+ Define your standard parameters once, use them everywhere:
195
+
196
+ .. code-block:: python
197
+
198
+ from voxelops import run_qsiprep, QSIPrepInputs, QSIPrepDefaults
199
+
200
+ BRAIN_BANK_QSIPREP = QSIPrepDefaults(
201
+ nprocs=16,
202
+ mem_mb=32000,
203
+ output_resolution=1.6,
204
+ anatomical_template=["MNI152NLin2009cAsym"],
205
+ docker_image="pennlinc/qsiprep:latest",
206
+ )
207
+
208
+ for participant in participants:
209
+ inputs = QSIPrepInputs(bids_dir=bids_root, participant=participant)
210
+ result = run_qsiprep(inputs, config=BRAIN_BANK_QSIPREP)
211
+ db.save_processing_record(result)
212
+
213
+ Documentation
214
+ -------------
215
+
216
+ Full documentation is available at `voxelops.readthedocs.io <https://voxelops.readthedocs.io>`_.
217
+
218
+ License
219
+ -------
220
+
221
+ MIT License -- see the `LICENSE <LICENSE>`_ file for details.