OSCR

MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice.

Code ↔ Paper

17 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 17 matches
  1. [1] § Methods › SynthSeg-driven segmentation and labeling ↔ lamareg/scripts/synthseg.py, lines 1–46 · score 0.84 · neural network, segment brain, contrast agnostic, MRI contrast, SynthSeg, deep
  2. [2] § Methods › SynthSeg-driven segmentation and labeling ↔ micaflow/scripts/synthseg.py, lines 205–296 · score 0.80 · contrast agnostic brain, neural network, MRI contrast, SynthSeg, T2w, anatomical
  3. [3] § Methods › Pipeline overview and architecture ↔ micaflow/cli.py, lines 221–358 · score 0.79 · BIDS batch, DICE scores, transformation matrices, SynthSeg, distortion corrected, derivative
  4. [4] § Methods › Diffusion MRI preprocessing › Susceptibility distortion correction ↔ micaflow/scripts/apply_SDC.py, lines 1–48 · score 0.78 · field inhomogeneity, phase encoding directions, displacement field, diffusion images, unwarps, geometrically
  5. [5] § Methods › Pipeline overview and architecture ↔ micaflow/scripts/motion_correction.py, lines 207–333 · score 0.71 · tensor fitting, bias field correction, brain extraction, distortion correction, eddy, denoising
  6. [6] § Methods › Pipeline overview and architecture ↔ micaflow/cli.py, lines 221–358 · score 0.71 · bias field correction, brain extraction, Snakemake, distortion correction, MicaFlow, configured
  7. [7] § Methods › Diffusion MRI preprocessing › Susceptibility distortion correction ↔ micaflow/scripts/SDC.py, lines 1–82 · score 0.69 · PyTorch, unwarps, blip, Hyperelastic, opposite, inhomogeneity
  8. [8] § Methods › Diffusion MRI preprocessing › Motion and Eddy current correction ↔ micaflow/scripts/motion_correction.py, lines 207–333 · score 0.66 · subject motion, gradient direction, stretch, eddy, shear, geometrical
  9. [9] § Methods › Diffusion MRI preprocessing › Diffusion metric mapping ↔ micaflow/scripts/compute_fa_md.py, lines 486–547 · score 0.65 · diffusion tensor model, fitted tensor, DIPY, FA, maps, metric
  10. [10] § Methods › Diffusion MRI preprocessing › Susceptibility distortion correction ↔ micaflow/scripts/SDC.py, lines 432–497 · score 0.64 · phase encoding axis, geometric distortions, field maps, MICAFlow
  11. [11] § Methods › Diffusion MRI preprocessing ↔ micaflow/scripts/denoise.py, lines 159–258 · score 0.64 · diffusion weighted, diffusion metrics, gradient directions, raw, preprocessing, scans
  12. [12] § Methods › Diffusion MRI preprocessing › Denoising and Gibbs correction ↔ micaflow/scripts/denoise.py, lines 159–258 · score 0.61 · Gibbs ringing, Patch2Self, denoising, noise, algorithm, preprocessing
  13. [13] § Methods › Diffusion MRI preprocessing › Susceptibility distortion correction ↔ micaflow/scripts/apply_SDC.py, lines 123–169 · score 0.58 · susceptibility induced, geometric distortions, phase encoded, axis, field, maps
  14. [14] § Methods › Pipeline overview and architecture ↔ micaflow/cli.py, lines 361–443 · score 0.57 · MICAFlow pipeline, Snakemake, execution, interface, Python, command
  15. [15] § Methods › Structural MRI preprocessing ↔ micaflow/scripts/bias_correction.py, lines 558–645 · score 0.52 · N4 bias field, bias correction, T2w, ANTs, anatomical, intensity
  16. [16] § Methods › Structural MRI preprocessing ↔ micaflow/scripts/coregister.py, lines 169–292 · score 0.52 · standard space, LAMAReg, MNI152, matching, nonlinear, anatomical
  17. [17] § Results ↔ lamareg/scripts/coregister.py, lines 406–463 · score 0.50 · ANTsPy, environment variables, thread, linear, LAMAReg

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 1,925 lines · 85 KB · GPL-3.0 · 3 matches

  1. #!/usr/bin/env python3
  2. """
  3. cli - Command-Line Interface for MicaFlow MRI Processing Pipeline
  4. This module provides the main command-line interface (CLI) for the MicaFlow
  5. neuroimaging processing pipeline. It handles command routing, argument parsing,
  6. and execution of both the full pipeline and individual processing modules.
  7. Architecture:
  8. ------------
  9. The CLI uses a two-level command structure:
  10. 1. Main command: 'micaflow [command]' routes to either the pipeline or a module
  11. 2. Module commands: Each processing module has its own subcommand with specific arguments
  12. The CLI intercepts help requests before argparse to provide custom, color-coded help
  13. messages for each command. Arguments are dynamically transformed and forwarded to
  14. the appropriate processing scripts.
  15. Available Commands:
  16. ------------------
  17. Pipeline Command:
  18. pipeline : Run the full Snakemake-based processing pipeline
  19. Preprocessing Modules:
  20. bet : Brain extraction using HD-BET
  21. bias_correction : N4 bias field correction
  22. denoise : Patch2Self denoising for DWI
  23. motion_correction : Motion correction for DWI
  24. normalize_intensity: Normalize image intensity profiles
  25. Registration Modules:
  26. coregister : Image coregistration using ANTs
  27. apply_warp : Apply transformations to images
  28. Distortion Correction:
  29. SDC : Susceptibility distortion correction
  30. apply_SDC : Apply precomputed SDC warp fields
  31. synth_b0 : Synthetic B0 generation
  32. Segmentation and Analysis:
  33. synthseg : SynthSeg brain segmentation
  34. texture_generation: Texture feature extraction
  35. compute_fa_md : DTI metric computation
  36. Utilities:
  37. extract_b0 : Extract b=0 volumes from DWI
  38. calculate_dice : DICE coefficient calculation
  39. Command Routing:
  40. ---------------
  41. 1. User runs: micaflow [command] [args...]
  42. 2. CLI checks for help flags and intercepts if present
  43. 3. Command is routed to either:
  44. - Snakemake pipeline (for 'pipeline' command)
  45. - Individual script (for module commands)
  46. 4. Arguments are formatted and passed to the target
  47. Argument Transformation:
  48. -----------------------
  49. The CLI transforms Python-style argument names (with underscores) to
  50. command-line style (with hyphens) automatically:
  51. Python: output_mask -> CLI: --output-mask
  52. Python: direction_dimension -> CLI: --direction-dimension
  53. This ensures consistency between the CLI parser and individual scripts.
  54. Features:
  55. --------
  56. - Color-coded help messages using colorama
  57. - Custom help formatting with extended descriptions
  58. - Automatic help interception for subcommands
  59. - Dynamic argument forwarding to processing scripts
  60. - Support for both pipeline and standalone module execution
  61. - Comprehensive error handling and reporting
  62. - Dry-run support for pipeline execution
  63. Exit Codes:
  64. ----------
  65. 0 : Success - command completed successfully
  66. 1 : Error - command failed, file not found, or invalid arguments
  67. Examples:
  68. --------
  69. # Show main help
  70. $ micaflow
  71. $ micaflow --help
  72. # Show help for specific command
  73. $ micaflow bet --help
  74. $ micaflow synthseg -h
  75. # Run full pipeline
  76. $ micaflow pipeline --subject sub-001 --t1w-file t1.nii.gz --output /out
  77. # Run individual module
  78. $ micaflow bet --input t1.nii.gz --output brain.nii.gz --output-mask mask.nii.gz
  79. # Dry run (pipeline only)
  80. $ micaflow pipeline --dry-run --subject sub-001 --t1w-file t1.nii.gz
  81. Notes:
  82. -----
  83. - The CLI uses subprocess.run() to execute individual scripts
  84. - All scripts are invoked as Python modules: python -m micaflow.scripts.[name]
  85. - The pipeline command uses Snakemake for workflow management
  86. - Configuration can be provided via command-line args or YAML config file
  87. """
  88. import argparse
  89. import sys
  90. import subprocess
  91. import glob
  92. import os
  93. import time
  94. import json
  95. import datetime
  96. import urllib.request
  97. import zipfile
  98. import tempfile
  99. import shutil
  100. from colorama import init, Fore, Style
  101. import importlib.resources
  102. init()
  103. def check_and_download_models():
  104. """
  105. Checks if the models and atlas directories are populated.
  106. If not, downloads and extracts them from the MICAFlow-Models GitHub repository.
  107. """
  108. base_path = os.path.dirname(os.path.abspath(__file__))
  109. models_dir = os.path.join(base_path, "models")
  110. atlas_dir = os.path.join(base_path, "resources", "atlas")
  111. # Check if directories exist and have files
  112. models_full = os.path.exists(models_dir) and len(os.listdir(models_dir)) > 0
  113. atlas_full = os.path.exists(atlas_dir) and len(os.listdir(atlas_dir)) > 0
  114. if models_full and atlas_full:
  115. return
  116. print(f"{Fore.YELLOW}Required models or atlases are missing. Downloading from MICAFlow-Models repository...{Style.RESET_ALL}")
  117. os.makedirs(models_dir, exist_ok=True)
  118. os.makedirs(atlas_dir, exist_ok=True)
  119. url = "https://github.com/MICA-MNI/MICAFlow-Models/archive/refs/heads/main.zip"
  120. try:
  121. with tempfile.TemporaryDirectory() as temp_dir:
  122. zip_path = os.path.join(temp_dir, "models.zip")
  123. print(f"{Fore.CYAN}Downloading {url}...{Style.RESET_ALL}")
  124. urllib.request.urlretrieve(url, zip_path)
  125. print(f"{Fore.CYAN}Extracting files...{Style.RESET_ALL}")
  126. with zipfile.ZipFile(zip_path, 'r') as zip_ref:
  127. zip_ref.extractall(temp_dir)
  128. repo_dir = os.path.join(temp_dir, "MICAFlow-Models-main")
  129. # Map elements from repo to the tool's expected locations
  130. repo_models = os.path.join(repo_dir, "models")
  131. if os.path.exists(repo_models):
  132. shutil.copytree(repo_models, models_dir, dirs_exist_ok=True)
  133. repo_atlas = os.path.join(repo_dir, "resources", "atlas")
  134. repo_atlas_alt = os.path.join(repo_dir, "atlas")
  135. if os.path.exists(repo_atlas):
  136. shutil.copytree(repo_atlas, atlas_dir, dirs_exist_ok=True)
  137. elif os.path.exists(repo_atlas_alt):
  138. shutil.copytree(repo_atlas_alt, atlas_dir, dirs_exist_ok=True)
  139. print(f"{Fore.GREEN}Models and atlases downloaded successfully!{Style.RESET_ALL}")
  140. except Exception as e:
  141. print(f"{Fore.RED}Failed to download or extract models: {e}{Style.RESET_ALL}")
  142. def get_snakefile_path():
  143. """
  144. Get the path to the Snakefile within the installed package using importlib.resources.
  145. Returns
  146. -------
  147. str
  148. Absolute path to the Snakefile for use with Snakemake.
  149. Raises
  150. ------
  151. pkg_resources.DistributionNotFound
  152. If the micaflow package is not installed.
  153. pkg_resources.ResourceNotFound
  154. If the Snakefile is not found in the package resources.
  155. Examples
  156. --------
  157. >>> snakefile = get_snakefile_path()
  158. >>> print(snakefile)
  159. /path/to/site-packages/micaflow/resources/Snakefile
  160. >>> # Use with snakemake command
  161. >>> cmd = ['snakemake', '-s', get_snakefile_path(), '--cores', '4']
  162. Notes
  163. -----
  164. - The Snakefile must be in micaflow/resources/Snakefile
  165. - Path is resolved at runtime based on installation location
  166. - Used exclusively by the 'pipeline' command
  167. """
  168. try:
  169. # Python 3.9+: files() returns a Traversable object
  170. snakefile = importlib.resources.files("micaflow.resources").joinpath("Snakefile")
  171. return str(snakefile)
  172. except Exception:
  173. # Fallback for older Python versions
  174. import pkg_resources
  175. return pkg_resources.resource_filename("micaflow", "resources/Snakefile")
  176. def print_extended_help():
  177. # ANSI color codes
  178. CYAN = Fore.CYAN
  179. GREEN = Fore.GREEN
  180. YELLOW = Fore.YELLOW
  181. BLUE = Fore.BLUE
  182. MAGENTA = Fore.MAGENTA
  183. BOLD = Style.BRIGHT
  184. RESET = Style.RESET_ALL
  185. help_msg = f"""
  186. {CYAN}{BOLD}╔════════════════════════════════════════════════════════════════╗
  187. ║ MICAFLOW MRI PROCESSING PIPELINE ║
  188. ╚════════════════════════════════════════════════════════════════╝{RESET}
  189. MicaFlow is a comprehensive pipeline for processing structural and
  190. diffusion MRI data, with various modules that can be used independently.
  191. {CYAN}{BOLD}────────────────────────── USAGE ──────────────────────────{RESET}
  192. micaflow {GREEN}[command]{RESET} [options]
  193. micaflow {GREEN}pipeline{RESET} [options]
  194. micaflow {GREEN}bids{RESET} [options]
  195. {CYAN}{BOLD}─────────────────── AVAILABLE COMMANDS ───────────────────{RESET}
  196. {GREEN}pipeline{RESET} : Run the full processing pipeline (default)
  197. {GREEN}bids{RESET} : Run the pipeline on an entire BIDS dataset (batch mode)
  198. {GREEN}apply_warp{RESET} : Apply transformation to warp an image to a reference space
  199. {GREEN}bet{RESET} : Run brain extraction (using mask/SynthSeg)
  200. {GREEN}bias_correction{RESET} : Run N4 Bias Field Correction
  201. {GREEN}calculate_dice{RESET} : Calculate DICE score between two segmentations
  202. {GREEN}compute_fa_md{RESET} : Compute Fractional Anisotropy and Mean Diffusivity maps
  203. {GREEN}coregister{RESET} : Coregister a moving image to a reference image
  204. {GREEN}denoise{RESET} : Denoise diffusion-weighted images using Patch2Self
  205. {GREEN}motion_correction{RESET} : Perform motion correction on diffusion-weighted images
  206. {GREEN}normalize_intensity{RESET}: Normalize image intensity profiles
  207. {GREEN}SDC{RESET} : Run Susceptibility Distortion Correction on DWI images
  208. {GREEN}apply_SDC{RESET} : Apply pre-computed SDC warp field to an image
  209. {GREEN}synthseg{RESET} : Run SynthSeg brain segmentation
  210. {GREEN}texture_generation{RESET}: Generate texture features from neuroimaging data
  211. {CYAN}{BOLD}──────────────── PIPELINE REQUIRED PARAMETERS ────────────{RESET}
  212. {YELLOW}--subject{RESET} SUBJECT_ID Subject ID
  213. {YELLOW}--output{RESET} OUTPUT_DIR Output directory
  214. {YELLOW}--t1w-file{RESET} T1-weighted image file
  215. {CYAN}{BOLD}──────────────── PIPELINE OPTIONAL PARAMETERS ────────────{RESET}
  216. {YELLOW}--data-directory{RESET} DATA_DIR Input data directory
  217. {YELLOW}--session{RESET} SESSION_ID Session ID (default: none)
  218. {YELLOW}--flair-file{RESET} FLAIR_FILE FLAIR image file
  219. {YELLOW}--dwi-file{RESET} DWI_FILE Diffusion weighted image
  220. {YELLOW}--bval-file{RESET} B-value file for DWI
  221. {YELLOW}--bvec-file{RESET} B-vector file for DWI
  222. {YELLOW}--inverse-dwi-file{RESET} INV_FILE Inverse (PA) DWI for distortion correction
  223. {YELLOW}--inverse-bval-file{RESET} INV_BVAL Inverse b-value file for DWI
  224. {YELLOW}--inverse-bvec-file{RESET} INV_BVEC Inverse b-vector file for DWI
  225. {YELLOW}--gpu{RESET} Use GPU for computation
  226. {YELLOW}--cores{RESET} Number of CPU cores to use (default: 1)
  227. {YELLOW}--dry-run{RESET}, {YELLOW}-n{RESET} Dry run (don't execute commands)
  228. {YELLOW}--config-file{RESET} FILE Path to a YAML configuration file
  229. {YELLOW}--extract-brain{RESET} Generate brain-extracted versions of all outputs in a dedicated directory
  230. {YELLOW}--keep-temp{RESET} Keep temporary processing files (useful for debugging)
  231. {YELLOW}--rm-cerebellum{RESET} Remove cerebellum from brain extraction outputs
  232. {YELLOW}--PED{RESET} Phase encoding direction of DWI, options are: 'ap', 'pa', 'lr', 'rl', 'si', 'is' (default: 'pa')
  233. {YELLOW}--direction-dimension{RESET} Dimension of the DWI image referring to directions (default: 3)
  234. {YELLOW}--linear{RESET} Use linear-only registration to MNI space (if specified alone)
  235. {YELLOW}--nonlinear{RESET} Use nonlinear registration to MNI space (default if neither specified)
  236. {CYAN}{BOLD}─────────────────── BIDS BATCH USAGE ────────────────────{RESET}
  237. micaflow {GREEN}bids{RESET} {YELLOW}--bids-dir{RESET} PATH {YELLOW}--output-dir{RESET} PATH [options]
  238. {BLUE}# Process entire BIDS directory{RESET}
  239. micaflow {GREEN}bids{RESET} {YELLOW}--bids-dir{RESET} /data/bids {YELLOW}--output-dir{RESET} /data/derivatives \\
  240. {YELLOW}--cores{RESET} 4 {YELLOW}--gpu{RESET}
  241. {BLUE}# Process specific subjects with custom suffixes{RESET}
  242. micaflow {GREEN}bids{RESET} {YELLOW}--bids-dir{RESET} /data/bids {YELLOW}--output-dir{RESET} /data/derivatives \\
  243. {YELLOW}--participant-label{RESET} 001 002 {YELLOW}--dwi-suffix{RESET} dwi_acq-AP.nii.gz
  244. {CYAN}{BOLD}────────────────── EXAMPLE PIPELINE USAGE ───────────────{RESET}
  245. {BLUE}# Process a single subject with T1w only{RESET}
  246. micaflow {GREEN}pipeline{RESET} {YELLOW}--subject{RESET} sub-001 {YELLOW}--session{RESET} ses-01 \\
  247. {YELLOW}--data-directory{RESET} /data {YELLOW}--t1w-file{RESET} sub-001_ses-01_T1w.nii.gz \\
  248. {YELLOW}--output{RESET} /output {YELLOW}--cores{RESET} 4
  249. {BLUE}# Process with FLAIR and brain extraction{RESET}
  250. micaflow {GREEN}pipeline{RESET} {YELLOW}--subject{RESET} sub-001 {YELLOW}--session{RESET} ses-01 \\
  251. {YELLOW}--data-directory{RESET} /data {YELLOW}--t1w-file{RESET} sub-001_ses-01_T1w.nii.gz \\
  252. {YELLOW}--flair-file{RESET} sub-001_ses-01_FLAIR.nii.gz {YELLOW}--output{RESET} /output \\
  253. {YELLOW}--extract-brain{RESET} {YELLOW}--cores{RESET} 4
  254. {BLUE}# Process with diffusion data, keep temporary files and remove cerebellum{RESET}
  255. micaflow {GREEN}pipeline{RESET} {YELLOW}--subject{RESET} sub-001 {YELLOW}--session{RESET} ses-01 \\
  256. {YELLOW}--data-directory{RESET} /data {YELLOW}--t1w-file{RESET} sub-001_ses-01_T1w.nii.gz \\
  257. {YELLOW}--dwi-file{RESET} sub-001_ses-01_dwi.nii.gz \\
  258. {YELLOW}--bval-file{RESET} sub-001_ses-01_dwi.bval {YELLOW}--bvec-file{RESET} sub-001_ses-01_dwi.bvec \\
  259. {YELLOW}--inverse-dwi-file{RESET} sub-001_ses-01_acq-PA_dwi.nii.gz \\
  260. {YELLOW}--output{RESET} /output {YELLOW}--keep-temp{RESET} {YELLOW}--rm-cerebellum{RESET} {YELLOW}--cores{RESET} 4
  261. {CYAN}{BOLD}─────────────────── EXAMPLE MODULE USAGE ────────────────{RESET}
  262. {BLUE}# Run brain extraction{RESET}
  263. micaflow {GREEN}bet{RESET} {YELLOW}--input{RESET} t1w.nii.gz {YELLOW}--output{RESET} brain.nii.gz {YELLOW}--output-mask{RESET} mask.nii.gz
  264. {BLUE}# Run brain extraction with cerebellum removal{RESET}
  265. micaflow {GREEN}bet{RESET} {YELLOW}--input{RESET} t1w.nii.gz {YELLOW}--output{RESET} brain.nii.gz {YELLOW}--output-mask{RESET} mask.nii.gz {YELLOW}--remove-cerebellum{RESET}
  266. {BLUE}# Run SynthSeg segmentation{RESET}
  267. micaflow {GREEN}synthseg{RESET} {YELLOW}--i{RESET} t1w.nii.gz {YELLOW}--o{RESET} segmentation.nii.gz {YELLOW}--parc{RESET}
  268. {BLUE}# Apply warp transformation{RESET}
  269. micaflow {GREEN}apply_warp{RESET} {YELLOW}--moving{RESET} t1w.nii.gz {YELLOW}--reference{RESET} template.nii.gz \\
  270. {YELLOW}--warp{RESET} warp.nii.gz {YELLOW}--affine{RESET} transform.mat {YELLOW}--output{RESET} warped.nii.gz
  271. {CYAN}{BOLD}───────────────── OUTPUT DIRECTORY STRUCTURE ────────────{RESET}
  272. output/
  273. └── <subject>/
  274. └── <session>/
  275. ├── anat/ # Anatomical images (bias-corrected)
  276. ├── brain-extracted/ # Brain-extracted images (with --extract-brain)
  277. ├── dwi/ # Processed diffusion data and DTI metrics
  278. ├── metrics/ # Quality metrics and DICE scores
  279. ├── temp/ # Temporary files (preserved with --keep-temp)
  280. ├── textures/ # Texture features
  281. └── xfm/ # Transformation matrices and warps
  282. {CYAN}{BOLD}────────────────────────── NOTES ───────────────────────{RESET}
  283. {MAGENTA}•{RESET} For help on a specific module: micaflow [command] --help
  284. {MAGENTA}•{RESET} The pipeline uses Snakemake for workflow management
  285. {MAGENTA}•{RESET} Config file can be used to specify parameters instead of command line options
  286. {MAGENTA}•{RESET} Each module can be run independently with its own set of parameters
  287. {MAGENTA}•{RESET} Use --extract-brain to generate skull-stripped versions of all outputs in a dedicated directory
  288. {MAGENTA}•{RESET} Use --keep-temp to preserve intermediate files (useful for debugging)
  289. {MAGENTA}•{RESET} Use --rm-cerebellum to remove cerebellum from brain extraction outputs
  290. For more detailed help on any command, use: micaflow {GREEN}[command]{RESET} {YELLOW}--help{RESET}
  291. """
  292. return help_msg
  293. def main():
  294. """
  295. Main entry point for the MicaFlow command-line interface.
  296. This function handles all CLI operations including:
  297. - Argument parsing for pipeline and module commands
  298. - Help message interception and display
  299. - Command routing to appropriate handlers
  300. - Dynamic argument transformation and forwarding
  301. - Error handling and reporting
  302. The function uses a two-level command structure where the first argument
  303. determines whether to run the full pipeline or an individual module.
  304. Flow:
  305. -----
  306. 1. Check for help flags or no arguments -> display help and exit
  307. 2. Intercept subcommand help requests before argparse processing
  308. 3. Parse arguments using argparse with custom formatter
  309. 4. Route to appropriate command handler:
  310. - 'pipeline': Build and execute Snakemake command
  311. - Module commands: Transform args and execute module script
  312. 5. Handle errors and report results
  313. Command Routing:
  314. ---------------
  315. Pipeline Command:
  316. - Collects configuration parameters
  317. - Builds Snakemake command with --config options
  318. - Optionally loads YAML config file
  319. - Executes via subprocess.run()
  320. Module Commands:
  321. - Transform Python-style args to CLI-style (underscores to hyphens)
  322. - Build command-line argument list
  323. - Execute via subprocess.run() with python -m
  324. - Report results and any errors
  325. Argument Transformation:
  326. -----------------------
  327. For module commands, arguments are transformed:
  328. - Python: output_mask -> CLI: --output-mask
  329. - Python: direction_dimension -> CLI: --direction-dimension
  330. - Boolean flags: Included only if True
  331. - Lists: Expanded into multiple values
  332. Exit Codes:
  333. ----------
  334. 0 : Success - command completed
  335. 1 : Error - command failed or invalid arguments
  336. Examples
  337. --------
  338. >>> # Run from command line
  339. >>> # micaflow
  340. >>> # micaflow pipeline --subject sub-001 --t1w-file t1.nii.gz
  341. >>> # micaflow bet --input t1.nii.gz --output brain.nii.gz
  342. >>> # Can also be called programmatically
  343. >>> if __name__ == "__main__":
  344. ... main()
  345. Raises
  346. ------
  347. subprocess.CalledProcessError
  348. If a module or pipeline command fails during execution.
  349. SystemExit
  350. Called with exit code 0 or 1 depending on success/failure.
  351. Notes
  352. -----
  353. - Uses subprocess.run() for all command execution
  354. - Scripts executed as Python modules: python -m micaflow.scripts.[name]
  355. - Help is intercepted before argparse to allow custom formatting
  356. - Unknown arguments passed through to Snakemake for pipeline command
  357. - Print statements used for user feedback (not logging framework)
  358. See Also
  359. --------
  360. print_extended_help : Extended help message display
  361. get_snakefile_path : Get path to pipeline Snakefile
  362. """
  363. check_and_download_models()
  364. # If no arguments provided, show help and exit
  365. if len(sys.argv) == 1:
  366. print(print_extended_help())
  367. sys.exit(0)
  368. # Intercept help requests for subcommands before argparse processes them
  369. # This allows us to show custom help for individual modules
  370. if (len(sys.argv) >= 3 and sys.argv[2] in ["-h", "--help"]) or len(sys.argv) == 2:
  371. # Check if the first argument is a valid command
  372. command = sys.argv[1]
  373. # Map of command names to their Python module paths
  374. script_map = {
  375. "apply_warp": "micaflow.scripts.apply_warp",
  376. "bet": "micaflow.scripts.bet",
  377. "bias_correction": "micaflow.scripts.bias_correction",
  378. "calculate_dice": "micaflow.scripts.calculate_dice",
  379. "compute_fa_md": "micaflow.scripts.compute_fa_md",
  380. "coregister": "micaflow.scripts.coregister",
  381. "denoise": "micaflow.scripts.denoise",
  382. "motion_correction": "micaflow.scripts.motion_correction",
  383. "SDC": "micaflow.scripts.SDC",
  384. "apply_SDC": "micaflow.scripts.apply_SDC",
  385. "synthseg": "micaflow.scripts.synthseg",
  386. "texture_generation": "micaflow.scripts.texture_generation",
  387. "normalize": "micaflow.scripts.normalize",
  388. }
  389. if command in script_map:
  390. if command != "pipeline": # Special case for pipeline
  391. try:
  392. print(f"\n=== Help for '{command}' command ===\n")
  393. subprocess.run(
  394. ["python", "-m", script_map[command], "--help"], check=True
  395. )
  396. sys.exit(0)
  397. except subprocess.CalledProcessError as e:
  398. print(f"Error displaying help for {command}: {e}")
  399. sys.exit(1)
  400. # Create custom formatter that includes our extended help
  401. class CustomHelpFormatter(argparse.HelpFormatter):
  402. """
  403. Custom help formatter for argparse that displays extended help.
  404. This formatter overrides the default argparse help to show the
  405. comprehensive, color-coded help message from print_extended_help().
  406. """
  407. def __init__(self, *args, **kwargs):
  408. super().__init__(*args, **kwargs, width=100)
  409. def format_help(self):
  410. # Include standard argparse help and our extended help
  411. standard_help = super().format_help()
  412. if "-h" in sys.argv or "--help" in sys.argv:
  413. return print_extended_help()
  414. return standard_help
  415. parser = argparse.ArgumentParser(
  416. description="Run the micaflow MRI processing pipeline",
  417. formatter_class=CustomHelpFormatter,
  418. )
  419. # Create subparsers for different commands
  420. subparsers = parser.add_subparsers(dest="command", help="Commands")
  421. # Add arguments that match the config parameters
  422. # Pipeline command (default)
  423. pipeline_parser = subparsers.add_parser(
  424. "pipeline", help="Run the full micaflow pipeline"
  425. )
  426. # Add pipeline arguments
  427. pipeline_parser.add_argument("--subject", required=True, help="Subject ID (e.g., sub-01)")
  428. pipeline_parser.add_argument("--session", help="Session ID (e.g., ses-01)")
  429. # CHANGE: --output to --output-dir
  430. pipeline_parser.add_argument("--output-dir", required=True, help="Output directory")
  431. # CHANGE: --data-directory to --data-dir to match --bids-dir naming convention
  432. pipeline_parser.add_argument(
  433. "--data-dir", default="", help="Data directory path"
  434. )
  435. pipeline_parser.add_argument("--flair-file", help="Path to FLAIR image")
  436. pipeline_parser.add_argument("--t1w-file", required=True, help="Path to T1w image")
  437. pipeline_parser.add_argument("--dwi-file", help="Path to DWI image")
  438. pipeline_parser.add_argument("--bval-file", help="Path to bval file")
  439. pipeline_parser.add_argument("--bvec-file", help="Path to bvec file")
  440. pipeline_parser.add_argument("--inverse-dwi-file", help="Path to inverse DWI file")
  441. pipeline_parser.add_argument("--inverse-bval-file", help="Path to inverse bval file")
  442. pipeline_parser.add_argument("--inverse-bvec-file", help="Path to inverse bvec file")
  443. pipeline_parser.add_argument(
  444. "--gpu", action="store_true", help="Use GPU computation"
  445. )
  446. pipeline_parser.add_argument(
  447. "--dry-run", "-n", action="store_true", help="Dry run (don't execute commands)"
  448. )
  449. pipeline_parser.add_argument(
  450. "--cores", type=int, default=1,
  451. help="Number of Snakemake jobs to run in parallel (Snakemake --cores)"
  452. )
  453. pipeline_parser.add_argument(
  454. "--config-file", help="Path to a YAML configuration file"
  455. )
  456. pipeline_parser.add_argument(
  457. "--rm-cerebellum", action="store_true", help="Remove cerebellum from images"
  458. )
  459. pipeline_parser.add_argument(
  460. "--keep-temp", action="store_true", help="Keep temporary files after processing"
  461. )
  462. pipeline_parser.add_argument(
  463. "--extract-brain", action="store_true", help="Keep brain-extracted images"
  464. )
  465. pipeline_parser.add_argument(
  466. "--PED", default="pa", help="Phase encoding direction of DWI, options are: 'ap', 'pa', 'lr', 'rl', 'si', 'is'"
  467. )
  468. pipeline_parser.add_argument(
  469. "--direction-dimension", type=int, default=3, help="Dimension of the DWI image referring to directions"
  470. )
  471. pipeline_parser.add_argument(
  472. "--linear", action="store_true", help="Use linear-only registration to MNI space (if specified alone)"
  473. )
  474. pipeline_parser.add_argument(
  475. "--nonlinear", action="store_true", help="Use nonlinear registration to MNI space (default if neither specified)"
  476. )
  477. # BIDS Batch Processing Command
  478. bids_parser = subparsers.add_parser(
  479. "bids", help="Run the pipeline on an entire BIDS dataset"
  480. )
  481. bids_parser.add_argument("--bids-dir", required=True, help="Path to BIDS root directory")
  482. bids_parser.add_argument("--output-dir", required=True, help="Path to derivatives/output directory")
  483. bids_parser.add_argument("--participant-label", nargs="+", help="Specific list of subjects to process (without 'sub-' prefix)")
  484. bids_parser.add_argument("--session-label", nargs="+", help="Specific list of sessions to process (without 'ses-' prefix)")
  485. # ADD THIS LINE:
  486. bids_parser.add_argument("--analysis-level", choices=["participant", "group"], default="participant", help="Processing level (participant or group)")
  487. # Suffix configuration
  488. bids_parser.add_argument("--t1w-suffix", default="T1w.nii.gz", help="Suffix for T1w images (default: T1w.nii.gz)")
  489. bids_parser.add_argument("--flair-suffix", help="Suffix for FLAIR images (e.g. FLAIR.nii.gz). If not provided, FLAIR is skipped.")
  490. bids_parser.add_argument("--dwi-suffix", help="Suffix for DWI images (e.g. dwi.nii.gz). If not provided, DWI is skipped.")
  491. bids_parser.add_argument("--inverse-dwi-suffix", help="Suffix for Inverse DWI images (e.g. acq-rpe_dwi.nii.gz). If not provided, ignored.")
  492. bids_parser.add_argument("--bval-suffix", help="Suffix for bval files (e.g. dwi.bval). If not provided, bval is skipped.")
  493. bids_parser.add_argument("--bvec-suffix", help="Suffix for bvec files (e.g. dwi.bvec). If not provided, bvec is skipped.")
  494. bids_parser.add_argument("--inverse-bval-suffix", help="Suffix for inverse bval files (e.g. acq-rpe_dwi.bval). If not provided, ignored.")
  495. bids_parser.add_argument("--inverse-bvec-suffix", help="Suffix for inverse bvec files (e.g. acq-rpe_dwi.bvec). If not provided, ignored.")
  496. # Passthrough arguments
  497. bids_parser.add_argument("--gpu", action="store_true", help="Use GPU computation")
  498. bids_parser.add_argument("--dry-run", "-n", action="store_true", help="Print commands without executing")
  499. bids_parser.add_argument("--cores", type=int, default=1, help="Number of cores per subject")
  500. bids_parser.add_argument("--rm-cerebellum", action="store_true", help="Remove cerebellum")
  501. bids_parser.add_argument("--extract-brain", action="store_true", help="Generate brain-extracted outputs")
  502. bids_parser.add_argument("--keep-temp", action="store_true", help="Keep temporary files")
  503. bids_parser.add_argument("--linear", action="store_true", help="Use linear-only registration")
  504. bids_parser.add_argument("--nonlinear", action="store_true", help="Use nonlinear registration")
  505. bids_parser.add_argument("--PED", default="pa", help="Phase encoding direction (default: pa)")
  506. bids_parser.add_argument("--direction-dimension", type=int, default=3, help="Direction dimension")
  507. bids_parser.add_argument("--config-file", help="YAML config file")
  508. # SynthSeg command
  509. synthseg_parser = subparsers.add_parser(
  510. "synthseg", help="Run SynthSeg brain segmentation"
  511. )
  512. synthseg_parser.add_argument(
  513. "--input", help="Image(s) to segment. Can be a path to an image or to a folder."
  514. )
  515. synthseg_parser.add_argument(
  516. "--output",
  517. help="Segmentation output(s). Must be a folder if --input designates a folder.",
  518. )
  519. synthseg_parser.add_argument(
  520. "--parc",
  521. action="store_true",
  522. help="(optional) Whether to perform cortex parcellation.",
  523. )
  524. synthseg_parser.add_argument(
  525. "--robust",
  526. action="store_true",
  527. help="(optional) Whether to use robust predictions (slower).",
  528. )
  529. synthseg_parser.add_argument(
  530. "--fast",
  531. action="store_true",
  532. help="(optional) Bypass some postprocessing for faster predictions.",
  533. )
  534. synthseg_parser.add_argument(
  535. "--ct",
  536. action="store_true",
  537. help="(optional) Clip intensities to [0,80] for CT scans.",
  538. )
  539. synthseg_parser.add_argument(
  540. "--vol",
  541. help="(optional) Path to output CSV file with volumes (mm3) for all regions and subjects.",
  542. )
  543. synthseg_parser.add_argument(
  544. "--qc",
  545. help="(optional) Path to output CSV file with qc scores for all subjects.",
  546. )
  547. synthseg_parser.add_argument(
  548. "--post",
  549. help="(optional) Posteriors output(s). Must be a folder if --i designates a folder.",
  550. )
  551. synthseg_parser.add_argument(
  552. "--resample",
  553. help="(optional) Resampled image(s). Must be a folder if --i designates a folder.",
  554. )
  555. synthseg_parser.add_argument(
  556. "--crop",
  557. nargs="+",
  558. type=int,
  559. help="(optional) Size of 3D patches to analyse. Default is 192.",
  560. )
  561. synthseg_parser.add_argument(
  562. "--threads", help="(optional) Number of cores to be used. Default is 1."
  563. )
  564. synthseg_parser.add_argument(
  565. "--cpu",
  566. action="store_true",
  567. help="(optional) Enforce running with CPU rather than GPU.",
  568. )
  569. synthseg_parser.add_argument(
  570. "--v1",
  571. action="store_true",
  572. help="(optional) Use SynthSeg 1.0 (updated 25/06/22).",
  573. )
  574. # SDC command
  575. apply_sdc_parser = subparsers.add_parser("apply_SDC")
  576. apply_sdc_parser.add_argument(
  577. "--input",
  578. required=True,
  579. help="Path to the motion-corrected DWI image (.nii.gz)",
  580. )
  581. apply_sdc_parser.add_argument(
  582. "--warp",
  583. required=True,
  584. help="Path to the warp field estimated from SDC (.nii.gz)",
  585. )
  586. apply_sdc_parser.add_argument(
  587. "--affine",
  588. required=True,
  589. help="Path to an image from which to extract the affine matrix",
  590. )
  591. apply_sdc_parser.add_argument(
  592. "--output", required=True, help="Output path for the corrected image"
  593. )
  594. # Apply Warp command
  595. apply_warp_parser = subparsers.add_parser(
  596. "apply_warp", help="Apply transformation to warp an image to a reference space"
  597. )
  598. apply_warp_parser.add_argument(
  599. "--moving", required=True, help="Path to the moving image that will be warped"
  600. )
  601. apply_warp_parser.add_argument(
  602. "--reference", required=True, help="Path to the reference/target image"
  603. )
  604. apply_warp_parser.add_argument(
  605. "--warp",
  606. help="Path to the warp field for non-linear transformation",
  607. )
  608. apply_warp_parser.add_argument(
  609. "--secondary-warp",
  610. help="Path to a secondary warp field to be applied after the primary warp and affine",
  611. )
  612. apply_warp_parser.add_argument(
  613. "--affine", help="Path to the affine transformation file"
  614. )
  615. apply_warp_parser.add_argument(
  616. "--output", required=True, help="Output path for the warped image"
  617. )
  618. apply_warp_parser.add_argument(
  619. "--interpolation",
  620. default="linear",
  621. help="Interpolation method (default: linear).",
  622. )
  623. apply_warp_parser.add_argument(
  624. "--transforms", nargs="+", help="List of transforms to apply in order (First -> Last)."
  625. )
  626. # Brain Extraction Tool command
  627. bet_parser = subparsers.add_parser("bet", help="Run Brain Extraction (using SynthSeg or Mask)")
  628. bet_parser.add_argument(
  629. "--input", required=True, help="Path to the input image (.nii.gz)"
  630. )
  631. bet_parser.add_argument(
  632. "--output",
  633. required=True,
  634. help="Path to the output brain-extracted image (.nii.gz)",
  635. )
  636. bet_parser.add_argument(
  637. "--output-mask", help="Path to the output brain mask (.nii.gz)"
  638. )
  639. bet_parser.add_argument(
  640. "--input-mask", help="Path to the input brain mask (.nii.gz) (optional)"
  641. )
  642. bet_parser.add_argument(
  643. "--parcellation", help="Parcellation file for the input image (optional)"
  644. )
  645. bet_parser.add_argument(
  646. "--remove-cerebellum",
  647. action="store_true",
  648. help="Remove cerebellum from the input image (optional)",
  649. )
  650. # Bias Correction Tool command
  651. bias_corr_parser = subparsers.add_parser(
  652. "bias_correction", help="Run N4 Bias Field Correction"
  653. )
  654. bias_corr_parser.add_argument(
  655. "--input", "-i", required=True, help="Path to the input image (.nii.gz)"
  656. )
  657. bias_corr_parser.add_argument(
  658. "--output", "-o", required=True, help="Path to the output bias-corrected image (.nii.gz)"
  659. )
  660. bias_corr_parser.add_argument(
  661. "--mask", help="Path to a mask image (required for 4D images, optional for 3D)"
  662. )
  663. bias_corr_parser.add_argument(
  664. "--mode",
  665. choices=["3d", "4d", "auto"],
  666. default="auto",
  667. help="Processing mode: 3d=anatomical, 4d=diffusion, auto=detect (default)",
  668. )
  669. bias_corr_parser.add_argument(
  670. "--b0", help="b0 image path, required for 4D diffusion images."
  671. )
  672. bias_corr_parser.add_argument(
  673. "--b0-output", help="Path for the output corrected b0 image (only for 4D DWI)."
  674. )
  675. bias_corr_parser.add_argument(
  676. "--direction-dimension",
  677. type=int,
  678. default=3,
  679. help="Dimension of the DWI image referring to directions (default: 3)",
  680. )
  681. bias_corr_parser.add_argument(
  682. "--threads",
  683. type=int,
  684. default=1,
  685. help="Number of threads to use for bias correction (default: 1)",
  686. )
  687. bias_corr_parser.add_argument(
  688. "--gibbs", action="store_true", help="Apply Gibbs ringing correction"
  689. )
  690. # DICE Calculator command
  691. dice_parser = subparsers.add_parser(
  692. "calculate_dice",
  693. help="Calculate DICE between two segmentations",
  694. )
  695. dice_parser.add_argument("--input", "-i", required=True, help="First input volume")
  696. dice_parser.add_argument(
  697. "--reference", "-r", required=True, help="Reference volume to compare against"
  698. )
  699. dice_parser.add_argument(
  700. "--output", "-o", required=True, help="Output CSV file path"
  701. )
  702. # Compute FA/MD command
  703. compute_fa_md_parser = subparsers.add_parser(
  704. "compute_fa_md", help="Compute Fractional Anisotropy and Mean Diffusivity maps"
  705. )
  706. compute_fa_md_parser.add_argument(
  707. "--input", required=True, help="Path to the preprocessed DWI image (.nii.gz)"
  708. )
  709. compute_fa_md_parser.add_argument(
  710. "--bval", required=True, help="Path to the b-values file (.bval)"
  711. )
  712. compute_fa_md_parser.add_argument(
  713. "--bvec", required=True, help="Path to the b-vectors file (.bvec)"
  714. )
  715. compute_fa_md_parser.add_argument(
  716. "--mask", help="Optional: Path to a brain mask (.nii.gz)"
  717. )
  718. compute_fa_md_parser.add_argument(
  719. "--output-fa",
  720. required=True,
  721. help="Output path for the Fractional Anisotropy map (.nii.gz)",
  722. )
  723. compute_fa_md_parser.add_argument(
  724. "--output-md",
  725. required=True,
  726. help="Output path for the Mean Diffusivity map (.nii.gz)",
  727. )
  728. compute_fa_md_parser.add_argument("--b0-volume", type=str,
  729. help="Path to the b0 volume to merge with DWI.")
  730. compute_fa_md_parser.add_argument("--b0-bval", type=str,
  731. help="Path to b0 b-value file.")
  732. compute_fa_md_parser.add_argument("--b0-bvec", type=str,
  733. help="Path to b0 b-vector file.")
  734. compute_fa_md_parser.add_argument("--b0-index", type=int, default=0,
  735. help="Index at which to insert b0 volume (default: 0).")
  736. # Coregistration command
  737. coreg_parser = subparsers.add_parser(
  738. "coregister", help="Coregister a moving image to a reference image using label-augmented registration"
  739. )
  740. coreg_parser.add_argument(
  741. "--fixed-file", required=True,
  742. help="Path to the fixed/reference image (.nii.gz)"
  743. )
  744. coreg_parser.add_argument(
  745. "--moving-file", required=True,
  746. help="Path to the moving image to be registered (.nii.gz)"
  747. )
  748. coreg_parser.add_argument(
  749. "--fixed-segmentation",
  750. help="Path to the fixed segmentation image (.nii.gz). If not provided, will be generated automatically."
  751. )
  752. coreg_parser.add_argument(
  753. "--moving-segmentation",
  754. help="Path to the moving segmentation image (.nii.gz). If not provided, will be generated automatically."
  755. )
  756. coreg_parser.add_argument(
  757. "--output", required=True,
  758. help="Output path for the registered image (.nii.gz)"
  759. )
  760. coreg_parser.add_argument(
  761. "--warp-file", default=None,
  762. help="Optional path to save the forward warp field (moving to fixed) (.nii.gz)"
  763. )
  764. coreg_parser.add_argument(
  765. "--secondary-warp-file", default=None,
  766. help="Optional path to save a secondary warp field to be applied after the primary warp and affine (.nii.gz)"
  767. )
  768. coreg_parser.add_argument(
  769. "--affine-file", default=None,
  770. help="Optional path to save the forward affine transform (moving to fixed) (.mat)"
  771. )
  772. coreg_parser.add_argument(
  773. "--rev-warp-file", default=None,
  774. help="Optional path to save the reverse warp field (fixed to moving) (.nii.gz)"
  775. )
  776. coreg_parser.add_argument(
  777. "--secondary-rev-warp-file", default=None,
  778. help="Optional path to save a secondary reverse warp field to be applied after the primary reverse warp and affine (.nii.gz)"
  779. )
  780. coreg_parser.add_argument(
  781. "--threads", type=int, default=1,
  782. help="Number of threads for registration operations (default: 1)"
  783. )
  784. coreg_parser.add_argument(
  785. "--output-segmentation",
  786. help="Path to save the transformed segmentation alongside the registered image (.nii.gz)"
  787. )
  788. coreg_parser.add_argument(
  789. "--linear-only", action='store_true',
  790. help="Perform only linear registration (rigid + affine) without nonlinear SyN warping (faster)"
  791. )
  792. coreg_parser.add_argument(
  793. "--disable-robust", action='store_true',
  794. help="If set, disables robust registration mode in LAMAReg."
  795. )
  796. # Denoise command
  797. denoise_parser = subparsers.add_parser(
  798. "denoise", help="Denoise diffusion-weighted images using Patch2Self"
  799. )
  800. denoise_parser.add_argument(
  801. "--input", required=True, help="Path to the input DWI image (.nii.gz)"
  802. )
  803. denoise_parser.add_argument(
  804. "--bval", required=True, help="Path to the b-values file (.bval)"
  805. )
  806. denoise_parser.add_argument(
  807. "--bvec", required=True, help="Path to the b-vectors file (.bvec)"
  808. )
  809. denoise_parser.add_argument(
  810. "--output", required=True, help="Output path for the denoised image (.nii.gz)"
  811. )
  812. denoise_parser.add_argument("--b0-denoise", action='store_true', help="Denoise b0 volumes separately (default: False)")
  813. denoise_parser.add_argument("--gibbs", action='store_true', help="Apply Gibbs ringing correction (default: False)")
  814. denoise_parser.add_argument("--threads", type=int, help="Number of threads to use (default: 1)")
  815. # Motion Correction command
  816. motion_corr_parser = subparsers.add_parser(
  817. "motion_correction",
  818. help="Perform motion correction on diffusion-weighted images",
  819. )
  820. motion_corr_parser.add_argument(
  821. "--denoised", required=True, help="Path to the denoised DWI (NIfTI file)."
  822. )
  823. motion_corr_parser.add_argument(
  824. "--input-bvals",
  825. type=str,
  826. required=True,
  827. help="Path to the bvals file.",
  828. )
  829. motion_corr_parser.add_argument(
  830. "--input-bvecs",
  831. type=str,
  832. required=True,
  833. help="Path to the bvecs file.",
  834. )
  835. motion_corr_parser.add_argument(
  836. "--output-bvecs",
  837. type=str,
  838. required=True,
  839. help="Path to the adjusted bvecs file.",
  840. )
  841. motion_corr_parser.add_argument(
  842. "--output", required=True, help="Output path for the motion-corrected DWI."
  843. )
  844. motion_corr_parser.add_argument(
  845. "--b0",
  846. type=str,
  847. help="Path to an external B0 image to use as reference. If not provided, the first volume is used.",
  848. )
  849. motion_corr_parser.add_argument(
  850. "--direction-dimension",
  851. type=int,
  852. default=3,
  853. help="Dimension of the DWI image referring to directions (default: 3)",
  854. )
  855. motion_corr_parser.add_argument(
  856. "--threads",
  857. type=int,
  858. help="Number of threads to use (default: 1)",
  859. )
  860. motion_corr_parser.add_argument(
  861. "--temp-dir",
  862. type=str,
  863. default="tmp",
  864. help="Path to the temporary directory for intermediate files (default: tmp)."
  865. )
  866. # SDC command (main susceptibility distortion correction)
  867. sdc_parser = subparsers.add_parser(
  868. "SDC", help="Run Susceptibility Distortion Correction on DWI images")
  869. sdc_parser.add_argument(
  870. "--input", required=True, help="Path to the data image (NIfTI file)")
  871. sdc_parser.add_argument(
  872. "--reverse-image",
  873. required=True,
  874. help="Path to the reverse phase-encoded image (NIfTI file)",
  875. )
  876. sdc_parser.add_argument(
  877. "--output",
  878. required=True,
  879. help="Output name for the corrected image (NIfTI file)",
  880. )
  881. sdc_parser.add_argument(
  882. "--output-warp",
  883. required=True,
  884. help="Output name for the warp field (NIfTI file)",
  885. )
  886. sdc_parser.add_argument(
  887. "--phase-encoding",
  888. type=str,
  889. default="ap",
  890. choices=["ap", "pa", "lr", "rl", "si", "is"],
  891. help="Phase-encoding direction (default: ap)"
  892. )
  893. # Texture Generation command
  894. texture_parser = subparsers.add_parser(
  895. "texture_generation", help="Generate texture features from neuroimaging data")
  896. texture_parser.add_argument(
  897. "--input", "-i", required=True, help="Path to the input image file (.nii.gz)")
  898. texture_parser.add_argument(
  899. "--mask", "-m", required=True, help="Path to the binary mask file (.nii.gz)")
  900. texture_parser.add_argument(
  901. "--output",
  902. "-o",
  903. required=True,
  904. help="Output directory for texture feature maps",
  905. )
  906. normalize_parser = subparsers.add_parser(
  907. "normalize", help="Normalize MRI intensity values")
  908. normalize_parser.add_argument(
  909. "--input", "-i", required=True, help="Input NIfTI image file (.nii.gz)")
  910. normalize_parser.add_argument(
  911. "--output", "-o", required=True, help="Output normalized image file (.nii.gz)")
  912. normalize_parser.add_argument(
  913. "--lower-percentile",
  914. type=float,
  915. default=1.0,
  916. help="Lower percentile for clamping (default: 1.0)",
  917. )
  918. normalize_parser.add_argument(
  919. "--upper-percentile",
  920. type=float,
  921. default=99.0,
  922. help="Upper percentile for clamping (default: 99.0)",
  923. )
  924. normalize_parser.add_argument(
  925. "--min-value",
  926. type=float,
  927. default=0,
  928. help="Minimum value in output range (default: 0)",
  929. )
  930. normalize_parser.add_argument(
  931. "--max-value",
  932. type=float,
  933. default=100,
  934. help="Maximum value in output range (default: 100)",
  935. )
  936. # Synthetic B0 Generation command
  937. synth_b0_parser = subparsers.add_parser(
  938. "synth_b0", help="Create a synthetic B0 image from T1w and distorted B0 images using an ensemble of models")
  939. synth_b0_parser.add_argument(
  940. '--t1', required=True, help='Path to T1w input image (.nii.gz)')
  941. synth_b0_parser.add_argument(
  942. '--b0', required=True, help='Path to distorted B0 input image (.nii.gz)')
  943. synth_b0_parser.add_argument(
  944. '--output', required=True, help='Path for synthetic B0 output image (.nii.gz)')
  945. synth_b0_parser.add_argument(
  946. '--intermediate', help='Path to save the synthetic B0 before inverse transform')
  947. synth_b0_parser.add_argument(
  948. '--cpu', action='store_true', help='Force CPU usage (default: use GPU if available)')
  949. synth_b0_parser.add_argument(
  950. '--phase-encoding', help='Index of the volume to extract from 4D DWI images (if applicable)')
  951. synth_b0_parser.add_argument(
  952. '--warp', help='Path to save the warp field')
  953. synth_b0_parser.add_argument(
  954. '--temp-dir', help='Path to a temporary directory for intermediate files')
  955. synth_b0_parser.add_argument(
  956. '--corrected-b0', help='Path to save the corrected B0 image (optional)')
  957. synth_b0_parser.add_argument(
  958. '--dwi', help='Path to the full DWI image')
  959. synth_b0_parser.add_argument(
  960. '--direction-dimension', type=int, default=3, help='Dimension of the DWI image referring to directions (default: 3)')
  961. synth_b0_parser.add_argument(
  962. '--threads', type=int, help='Number of threads to use (default: all)')
  963. synth_b0_parser.add_argument(
  964. '--b0-to-T1-warp', help='Path to save the warp field from B0 to T1w (optional)')
  965. synth_b0_parser.add_argument(
  966. '--b0-to-T1-warp-secondary', help='Path to a secondary warp field to be applied after the primary warp and affine (optional)')
  967. synth_b0_parser.add_argument(
  968. '--b0-to-T1-affine', help='Path to save the affine transform from B0 to T1w (optional)')
  969. synth_b0_parser.add_argument(
  970. '--b0-index', type=int, default=0,
  971. help="Index at which to insert b0 volume (default: 0).")
  972. # Extract B0 command
  973. extract_b0_parser = subparsers.add_parser(
  974. "extract_b0", help="Extract b=0 volume from DWI")
  975. extract_b0_parser.add_argument(
  976. "--input", required=True, help="Path to DWI image")
  977. extract_b0_parser.add_argument(
  978. "--bvals", help="Path to bvals file")
  979. extract_b0_parser.add_argument(
  980. "--bvecs", help="Path to bvecs file")
  981. extract_b0_parser.add_argument(
  982. "--output", required=True, help="Path for extracted b=0 volume")
  983. extract_b0_parser.add_argument(
  984. "--output-dwi", help="Path for output non-b0 volumes")
  985. extract_b0_parser.add_argument(
  986. "--output-bvals", help="Path for output bvals file")
  987. extract_b0_parser.add_argument(
  988. "--output-bvecs", help="Path for output bvecs file")
  989. extract_b0_parser.add_argument(
  990. "--threshold", type=float, default=50, help="Maximum b-value to consider as b=0 (default: 50)")
  991. extract_b0_parser.add_argument(
  992. "--index", type=int, help="Directly specify volume index to extract")
  993. extract_b0_parser.add_argument(
  994. "--direction-dimension", type=int, default=3, help="Dimension of the DWI image referring to directions (default: 3)")
  995. extract_b0_parser.add_argument("--b0-bval", help="Path for b0-only bval file")
  996. extract_b0_parser.add_argument("--b0-bvec", help="Path for b0-only bvec file")
  997. args, unknown = parser.parse_known_args()
  998. # If no command is provided, default to pipeline
  999. if not args.command:
  1000. args.command = "pipeline"
  1001. # ---> ADD THIS FUNCTION <---
  1002. def create_bids_dataset_description(out_dir):
  1003. """Creates a BIDS dataset_description.json in the derivative root directory."""
  1004. if not out_dir:
  1005. return
  1006. os.makedirs(out_dir, exist_ok=True)
  1007. desc_path = os.path.join(out_dir, "dataset_description.json")
  1008. if not os.path.exists(desc_path):
  1009. desc_data = {
  1010. "Name": "MICAFlow Derivatives",
  1011. "BIDSVersion": "1.8.0",
  1012. "DatasetType": "derivative",
  1013. "PipelineDescription": {
  1014. "Name": "MICAFlow"
  1015. }
  1016. }
  1017. try:
  1018. import json
  1019. with open(desc_path, "w") as f:
  1020. json.dump(desc_data, f, indent=4)
  1021. except Exception as e:
  1022. print(f"Warning: Could not create dataset_description.json: {e}")
  1023. if args.command == "bids":
  1024. # ADD THESE LINES TO HANDLE ANALYSIS LEVEL
  1025. if args.analysis_level == "group":
  1026. print("No group level analyses")
  1027. sys.exit(0)
  1028. # ---> ADD THIS CALL <---
  1029. create_bids_dataset_description(args.output_dir)
  1030. # 1. Identify Subjects
  1031. if args.participant_label:
  1032. subjects = [f"sub-{label}" if not label.startswith("sub-") else label for label in args.participant_label]
  1033. else:
  1034. subjects = [d for d in os.listdir(args.bids_dir) if d.startswith("sub-") and os.path.isdir(os.path.join(args.bids_dir, d))]
  1035. # Determine if 1-to-1 participant/session matching should be triggered
  1036. one_to_one = False
  1037. target_sessions = []
  1038. if args.participant_label and args.session_label and (len(args.participant_label) == len(args.session_label)):
  1039. one_to_one = True
  1040. target_sessions = [f"ses-{l}" if not l.startswith("ses-") else l for l in args.session_label]
  1041. elif args.session_label:
  1042. # Standard grouping if 1-to-1 isn't met (fallback for broadcasting sessions uniformly)
  1043. target_sessions = [f"ses-{l}" if not l.startswith("ses-") else l for l in args.session_label]
  1044. if not subjects:
  1045. print(f"{Fore.RED}No subjects found in {args.bids_dir}{Style.RESET_ALL}")
  1046. sys.exit(1)
  1047. print(f"{Fore.CYAN}Found {len(subjects)} subjects to process.{Style.RESET_ALL}")
  1048. # Helper to find unique files by suffix
  1049. def find_file(base, subfolder, suffix, desc):
  1050. path_pattern = os.path.join(base, subfolder, f"*{suffix}")
  1051. matches = glob.glob(path_pattern)
  1052. if len(matches) > 1:
  1053. print(f"{Fore.RED}Error: Multiple {desc} files found matching *{suffix} in {os.path.join(base, subfolder)}{Style.RESET_ALL}")
  1054. for m in matches:
  1055. print(f" - {os.path.basename(m)}")
  1056. return None, True # None found, Error=True
  1057. if len(matches) == 0:
  1058. return None, False # None found, Error=False
  1059. return matches[0], False # Found, Error=False
  1060. # 2. Iterate Subjects
  1061. for idx, sub in enumerate(subjects):
  1062. sub_dir = os.path.join(args.bids_dir, sub)
  1063. # Detect sessions based on matching structure
  1064. if one_to_one:
  1065. # Single session explicitly paired up with the subject
  1066. sessions = [target_sessions[idx]]
  1067. else:
  1068. sessions = [d for d in os.listdir(sub_dir) if d.startswith("ses-") and os.path.isdir(os.path.join(sub_dir, d))]
  1069. if not sessions:
  1070. sessions = [None] # No session structure
  1071. else:
  1072. sessions.sort()
  1073. # Filter sessions if requested generically
  1074. if target_sessions:
  1075. sessions = [s for s in sessions if s in target_sessions]
  1076. # 3. Iterate Sessions
  1077. for ses in sessions:
  1078. # Start Timer
  1079. start_time = time.time()
  1080. run_timestamp = datetime.datetime.now().isoformat()
  1081. # Define path
  1082. if ses:
  1083. base_path = os.path.join(sub_dir, ses)
  1084. ses_id = ses
  1085. sub_ses_str = f"{sub}/{ses}"
  1086. else:
  1087. base_path = sub_dir
  1088. ses_id = None
  1089. sub_ses_str = sub
  1090. print(f"\n{Fore.GREEN}Checking {sub_ses_str}...{Style.RESET_ALL}")
  1091. # 4. Find Files
  1092. # T1w (Required)
  1093. t1w, err = find_file(base_path, "anat", args.t1w_suffix, "T1w")
  1094. if err: continue # Skip on error (ambiguity)
  1095. if not t1w:
  1096. print(f"{Fore.YELLOW}Skipping {sub_ses_str}: No T1w found matching *{args.t1w_suffix}{Style.RESET_ALL}")
  1097. continue
  1098. # FLAIR (Optional)
  1099. flair = None
  1100. if args.flair_suffix:
  1101. flair, err = find_file(base_path, "anat", args.flair_suffix, "FLAIR")
  1102. if err: continue # Skip on ambiguity
  1103. # DWI (Optional)
  1104. dwi = None
  1105. if args.dwi_suffix:
  1106. dwi, err = find_file(base_path, "dwi", args.dwi_suffix, "DWI")
  1107. if err: continue # Skip on ambiguity
  1108. # Inverse DWI (Optional)
  1109. inv_dwi = None
  1110. if args.inverse_dwi_suffix:
  1111. inv_dwi, err = find_file(base_path, "dwi", args.inverse_dwi_suffix, "Inverse DWI")
  1112. if err: continue
  1113. # Check if DWI has bvals/bvecs (Required if DWI is present)
  1114. bval_file = None
  1115. bvec_file = None
  1116. if dwi:
  1117. # Infer bval/bvec by replacing extension
  1118. prefix = dwi
  1119. if prefix.endswith(".nii.gz"):
  1120. prefix = prefix[:-7]
  1121. elif prefix.endswith(".nii"):
  1122. prefix = prefix[:-4]
  1123. bval_cand = prefix + ".bval"
  1124. bvec_cand = prefix + ".bvec"
  1125. if os.path.exists(bval_cand) and os.path.exists(bvec_cand):
  1126. bval_file = bval_cand
  1127. bvec_file = bvec_cand
  1128. else:
  1129. # Fallback: Instead of skipping subject, just drop DWI and proceed with T1/FLAIR
  1130. print(f"{Fore.YELLOW}Warning: DWI found but missing .bval or .bvec files for {dwi}. Proceeding without DWI.{Style.RESET_ALL}")
  1131. dwi = None
  1132. # Check Inverse DWI bvals/bvecs
  1133. inv_bval_file = None
  1134. inv_bvec_file = None
  1135. if inv_dwi:
  1136. prefix = inv_dwi
  1137. if prefix.endswith(".nii.gz"):
  1138. prefix = prefix[:-7]
  1139. elif prefix.endswith(".nii"):
  1140. prefix = prefix[:-4]
  1141. cand_bval = prefix + ".bval"
  1142. cand_bvec = prefix + ".bvec"
  1143. if os.path.exists(cand_bval) and os.path.exists(cand_bvec):
  1144. inv_bval_file = cand_bval
  1145. inv_bvec_file = cand_bvec
  1146. # 5. Build Command
  1147. cmd = [sys.executable, "-m", "micaflow.cli", "pipeline"]
  1148. cmd.extend(["--subject", sub])
  1149. if ses_id:
  1150. cmd.extend(["--session", ses_id])
  1151. # CHANGE: Use --output-dir here instead of --output
  1152. cmd.extend(["--output-dir", args.output_dir])
  1153. # FIX: Do NOT pass data directory to avoid path duplication logic in pipeline.
  1154. # Instead, pass explicit absolute paths for all files.
  1155. # cmd.extend(["--data-directory", args.bids_dir])
  1156. cmd.extend(["--t1w-file", os.path.abspath(t1w)])
  1157. if flair:
  1158. cmd.extend(["--flair-file", os.path.abspath(flair)])
  1159. # DWI handling
  1160. if dwi:
  1161. cmd.extend(["--dwi-file", os.path.abspath(dwi)])
  1162. cmd.extend(["--bval-file", os.path.abspath(bval_file)])
  1163. cmd.extend(["--bvec-file", os.path.abspath(bvec_file)])
  1164. else:
  1165. # FIX: Explicitly pass empty strings to clear potential cache/defaults in pipeline
  1166. cmd.extend(["--dwi-file", ""])
  1167. cmd.extend(["--bval-file", ""])
  1168. cmd.extend(["--bvec-file", ""])
  1169. if inv_dwi:
  1170. cmd.extend(["--inverse-dwi-file", os.path.abspath(inv_dwi)])
  1171. cmd.extend(["--inverse-bval-file", os.path.abspath(inv_bval_file) if inv_bval_file else ""])
  1172. cmd.extend(["--inverse-bvec-file", os.path.abspath(inv_bvec_file) if inv_bvec_file else ""])
  1173. else:
  1174. cmd.extend(["--inverse-dwi-file", ""])
  1175. # Passthrough args
  1176. if args.gpu: cmd.append("--gpu")
  1177. if args.rm_cerebellum: cmd.append("--rm-cerebellum")
  1178. if args.extract_brain: cmd.append("--extract-brain")
  1179. if args.keep_temp: cmd.append("--keep-temp")
  1180. if args.linear: cmd.append("--linear")
  1181. if args.nonlinear: cmd.append("--nonlinear")
  1182. if args.config_file: cmd.extend(["--config-file", args.config_file])
  1183. cmd.extend(["--cores", str(args.cores)])
  1184. cmd.extend(["--PED", args.PED])
  1185. cmd.extend(["--direction-dimension", str(args.direction_dimension)])
  1186. # FIX: Pass unknown arguments (like --unlock, --rerun-incomplete) to the pipeline
  1187. if unknown:
  1188. cmd.extend(unknown)
  1189. # 6. Execute and Log
  1190. print(f"{Fore.CYAN}Launching pipeline for {sub_ses_str}...{Style.RESET_ALL}")
  1191. status = "unknown"
  1192. error_msg = None
  1193. if args.dry_run:
  1194. print(f"Dry run: {' '.join(cmd)}")
  1195. status = "dry_run"
  1196. else:
  1197. try:
  1198. subprocess.run(cmd, check=True)
  1199. status = "success"
  1200. except subprocess.CalledProcessError as e:
  1201. print(f"{Fore.RED}Pipeline failed for {sub_ses_str}{Style.RESET_ALL}")
  1202. status = "failed"
  1203. error_msg = str(e)
  1204. # 7. Generate Run Metadata JSON
  1205. # Logic updated: Append to a single summary JSON in the main output directory
  1206. if not args.dry_run:
  1207. try:
  1208. duration = time.time() - start_time
  1209. # Use main output dir
  1210. os.makedirs(args.output_dir, exist_ok=True)
  1211. metadata = {
  1212. "subject": sub,
  1213. "session": ses_id,
  1214. "timestamp": run_timestamp,
  1215. "duration_seconds": round(duration, 2),
  1216. "status": status,
  1217. "error": error_msg,
  1218. "inputs": {
  1219. "t1w": t1w,
  1220. "flair": flair,
  1221. "dwi": dwi,
  1222. "bval": bval_file,
  1223. "bvec": bvec_file,
  1224. "inverse_dwi": inv_dwi,
  1225. "inverse_bval": inv_bval_file,
  1226. "inverse_bvec": inv_bvec_file
  1227. },
  1228. "config": {
  1229. "linear": args.linear,
  1230. "nonlinear": args.nonlinear,
  1231. "ped": args.PED,
  1232. "direction_dimension": args.direction_dimension,
  1233. "extract_brain": args.extract_brain,
  1234. "rm_cerebellum": args.rm_cerebellum,
  1235. "gpu": args.gpu
  1236. },
  1237. "command_line": cmd
  1238. }
  1239. # Define the single summary log file path
  1240. json_path = os.path.join(args.output_dir, "micaflow_runs_summary.json")
  1241. # Load existing data if file exists
  1242. run_history = []
  1243. if os.path.exists(json_path):
  1244. try:
  1245. with open(json_path, "r") as f:
  1246. run_history = json.load(f)
  1247. if not isinstance(run_history, list):
  1248. # If for some reason it's not a list, wrap it or start new
  1249. # (Handling backward compatibility if it was dict)
  1250. run_history = []
  1251. except json.JSONDecodeError:
  1252. print(f"{Fore.YELLOW}Warning: Could not decode existing log file. Starting fresh.{Style.RESET_ALL}")
  1253. run_history = []
  1254. # Append new run
  1255. run_history.append(metadata)
  1256. # Write back to file
  1257. with open(json_path, "w") as f:
  1258. json.dump(run_history, f, indent=4)
  1259. print(f"Run metadata appended to {json_path}")
  1260. except Exception as e:
  1261. print(f"{Fore.RED}Error saving run metadata: {e}{Style.RESET_ALL}")
  1262. elif args.command == "pipeline":
  1263. # ---> ADD THIS CALL <---
  1264. # CHANGE: Use args.output_dir instead of args.output
  1265. create_bids_dataset_description(args.output_dir)
  1266. # Get the path to the Snakefile
  1267. snakefile = get_snakefile_path()
  1268. # Build the snakemake command
  1269. cmd = ["snakemake", "-s", snakefile]
  1270. # Add config parameters if provided
  1271. config = {}
  1272. for param in [
  1273. "subject",
  1274. "session",
  1275. "output_dir", # CHANGE THIS
  1276. "data_dir", # CHANGE THIS
  1277. "flair_file",
  1278. "t1w_file",
  1279. "dwi_file",
  1280. "bval_file",
  1281. "bvec_file",
  1282. "inverse_dwi_file",
  1283. "inverse_bval_file",
  1284. "inverse_bvec_file",
  1285. "phase_encoding_direction",
  1286. "rm_cerebellum",
  1287. "gpu",
  1288. "keep_temp",
  1289. "extract_brain",
  1290. "direction_dimension",
  1291. "PED",
  1292. "linear",
  1293. "nonlinear"
  1294. ]:
  1295. # FIX: Check if argument is strictly not None (allow empty strings to override cache)
  1296. val = getattr(args, param.replace("-", "_"), None)
  1297. if val is not None:
  1298. config[param] = val
  1299. # Add config parameters to command
  1300. if len(config) > 0:
  1301. cmd.append("--config")
  1302. for key, value in config.items():
  1303. cmd.extend([f"{key}={value}"])
  1304. # Add config file if provided
  1305. if args.config_file:
  1306. cmd.extend(["--configfile", args.config_file])
  1307. # Add other snakemake parameters
  1308. if args.dry_run:
  1309. cmd.append("-n")
  1310. cmd.extend(["--cores", str(args.cores)])
  1311. # Add any unknown arguments to pass to snakemake
  1312. if unknown:
  1313. cmd.extend(unknown)
  1314. print(f"Executing: {' '.join(cmd)}")
  1315. # Execute the snakemake command
  1316. try:
  1317. subprocess.run(cmd, check=True)
  1318. except subprocess.CalledProcessError as e:
  1319. print(f"Error running snakemake: {e}")
  1320. sys.exit(1)
  1321. elif args.command == "synthseg":
  1322. # Prepare arguments for SynthSeg
  1323. synthseg_args = []
  1324. for arg_name, arg_value in vars(args).items():
  1325. if arg_name != "command" and arg_value is not None:
  1326. # Map 'input' back to 'i' so the synthseg script recognizes it
  1327. passed_arg = "i" if arg_name == "input" else arg_name
  1328. if isinstance(arg_value, bool):
  1329. if arg_value:
  1330. synthseg_args.append(f"--{passed_arg}")
  1331. elif isinstance(arg_value, list):
  1332. synthseg_args.append(f"--{passed_arg}")
  1333. synthseg_args.extend([str(x) for x in arg_value])
  1334. else:
  1335. synthseg_args.append(f"--{passed_arg}")
  1336. synthseg_args.append(str(arg_value))
  1337. try:
  1338. print(f"Running SynthSeg brain segmentation on {args.input}...")
  1339. subprocess.run(
  1340. ["python", "-m", "micaflow.scripts.synthseg"] + synthseg_args,
  1341. check=True,
  1342. )
  1343. print(f"Brain segmentation completed. Output saved to {args.output}")
  1344. except subprocess.CalledProcessError as e:
  1345. print(f"Error running brain segmentation: {e}")
  1346. sys.exit(1)
  1347. elif args.command == "apply_SDC":
  1348. # Prepare arguments for apply_SDC
  1349. apply_sdc_parser = []
  1350. for arg_name, arg_value in vars(args).items():
  1351. if arg_name != "command" and arg_value is not None:
  1352. if isinstance(arg_value, bool):
  1353. if arg_value:
  1354. apply_sdc_parser.append(f"--{arg_name}")
  1355. elif isinstance(arg_value, list):
  1356. apply_sdc_parser.append(f"--{arg_name}")
  1357. apply_sdc_parser.extend([str(x) for x in arg_value])
  1358. else:
  1359. apply_sdc_parser.append(f"--{arg_name}")
  1360. apply_sdc_parser.append(str(arg_value))
  1361. # Run the apply_SDC script
  1362. try:
  1363. print(f"Applying susceptibility distortion correction to {args.input}...")
  1364. subprocess.run(
  1365. ["python", "-m", "micaflow.scripts.apply_SDC"] + apply_sdc_parser, check=True
  1366. )
  1367. print(
  1368. f"Susceptibility distortion correction completed. Output saved to {args.output}"
  1369. )
  1370. except subprocess.CalledProcessError as e:
  1371. print(f"Error applying susceptibility distortion correction: {e}")
  1372. sys.exit(1)
  1373. elif args.command == "apply_warp":
  1374. # Prepare arguments for apply_warp
  1375. apply_warp_args = []
  1376. for arg_name, arg_value in vars(args).items():
  1377. if arg_name != "command" and arg_value is not None:
  1378. arg_name_formatted = arg_name.replace("_", "-")
  1379. if isinstance(arg_value, bool):
  1380. if arg_value:
  1381. apply_warp_args.append(f"--{arg_name_formatted}")
  1382. elif isinstance(arg_value, list):
  1383. apply_warp_args.append(f"--{arg_name_formatted}")
  1384. apply_warp_args.extend([str(x) for x in arg_value])
  1385. else:
  1386. apply_warp_args.append(f"--{arg_name_formatted}")
  1387. apply_warp_args.append(str(arg_value))
  1388. try:
  1389. print(f"Applying warp transformation to {args.moving}...")
  1390. print(len(apply_warp_args))
  1391. subprocess.run(
  1392. ["python", "-m", "micaflow.scripts.apply_warp"] + apply_warp_args,
  1393. check=True,
  1394. )
  1395. print(f"Warp transformation completed. Output saved to {args.output}")
  1396. except subprocess.CalledProcessError as e:
  1397. print(f"Error applying warp transformation: {e}")
  1398. sys.exit(1)
  1399. elif args.command == "bet":
  1400. # Prepare arguments for bet
  1401. bet_args = []
  1402. for arg_name, arg_value in vars(args).items():
  1403. if arg_name != "command" and arg_value is not None:
  1404. # Convert underscores to hyphens in argument names for CLI compatibility
  1405. # This ensures --output_mask becomes --output-mask when passed to the script
  1406. arg_name_formatted = arg_name.replace("_", "-")
  1407. if isinstance(arg_value, bool):
  1408. if arg_value:
  1409. bet_args.append(f"--{arg_name_formatted}")
  1410. elif isinstance(arg_value, list):
  1411. bet_args.append(f"--{arg_name_formatted}")
  1412. bet_args.extend([str(x) for x in arg_value])
  1413. else:
  1414. bet_args.append(f"--{arg_name_formatted}")
  1415. bet_args.append(str(arg_value))
  1416. try:
  1417. print(f"Running brain extraction on {args.input}...")
  1418. subprocess.run(
  1419. ["python", "-m", "micaflow.scripts.bet"] + bet_args, check=True
  1420. )
  1421. print(f"Brain extraction completed. Output saved to {args.output}")
  1422. except subprocess.CalledProcessError as e:
  1423. print(f"Error running brain extraction: {e}")
  1424. sys.exit(1)
  1425. elif args.command == "bias_correction":
  1426. # Prepare arguments for bias_correction
  1427. bias_corr_args = []
  1428. for arg_name, arg_value in vars(args).items():
  1429. if arg_name != "command" and arg_value is not None:
  1430. arg_name_formatted = arg_name.replace("_", "-")
  1431. if isinstance(arg_value, bool):
  1432. if arg_value:
  1433. bias_corr_args.append(f"--{arg_name_formatted}")
  1434. elif isinstance(arg_value, list):
  1435. bias_corr_args.append(f"--{arg_name_formatted}")
  1436. bias_corr_args.extend([str(x) for x in arg_value])
  1437. else:
  1438. bias_corr_args.append(f"--{arg_name_formatted}")
  1439. bias_corr_args.append(str(arg_value))
  1440. # Run the bias_correction script
  1441. try:
  1442. print(f"Running bias field correction on {args.input}...")
  1443. subprocess.run(
  1444. ["python", "-m", "micaflow.scripts.bias_correction"] + bias_corr_args,
  1445. check=True,
  1446. )
  1447. print(f"Bias correction completed. Output saved to {args.output}")
  1448. except subprocess.CalledProcessError as e:
  1449. print(f"Error running bias correction: {e}")
  1450. sys.exit(1)
  1451. elif args.command == "calculate_dice":
  1452. # Prepare arguments for calculate_dice
  1453. dice_args = []
  1454. for arg_name, arg_value in vars(args).items():
  1455. if arg_name != "command" and arg_value is not None:
  1456. if isinstance(arg_value, bool):
  1457. if arg_value:
  1458. dice_args.append(f"--{arg_name}")
  1459. elif isinstance(arg_value, list):
  1460. dice_args.append(f"--{arg_name}")
  1461. dice_args.extend([str(x) for x in arg_value])
  1462. else:
  1463. dice_args.append(f"--{arg_name}")
  1464. dice_args.append(str(arg_value))
  1465. # Run the calculate_dice script
  1466. try:
  1467. print(f"Calculating DICE between {args.input} and {args.reference}...")
  1468. subprocess.run(
  1469. ["python", "-m", "micaflow.scripts.calculate_dice"] + dice_args,
  1470. check=True,
  1471. )
  1472. if args.output:
  1473. print(f"Results saved to {args.output}")
  1474. except subprocess.CalledProcessError as e:
  1475. print(f"Error calculating DICE: {e}")
  1476. sys.exit(1)
  1477. elif args.command == "compute_fa_md":
  1478. # Prepare arguments for compute_fa_md
  1479. compute_fa_md_args = []
  1480. for arg_name, arg_value in vars(args).items():
  1481. if arg_name != "command" and arg_value is not None:
  1482. arg_name_formatted = arg_name.replace("_", "-")
  1483. if isinstance(arg_value, bool):
  1484. if arg_value:
  1485. compute_fa_md_args.append(f"--{arg_name_formatted}")
  1486. elif isinstance(arg_value, list):
  1487. compute_fa_md_args.append(f"--{arg_name_formatted}")
  1488. compute_fa_md_args.extend([str(x) for x in arg_value])
  1489. else:
  1490. compute_fa_md_args.append(f"--{arg_name_formatted}")
  1491. compute_fa_md_args.append(str(arg_value))
  1492. # Run the compute_fa_md script
  1493. try:
  1494. print(f"Computing FA and MD maps from {args.input}...")
  1495. subprocess.run(
  1496. ["python", "-m", "micaflow.scripts.compute_fa_md"] + compute_fa_md_args,
  1497. check=True,
  1498. )
  1499. print(
  1500. f"DTI metrics computed. FA saved to {args.output_fa}, MD saved to {args.output_fa}"
  1501. )
  1502. except subprocess.CalledProcessError as e:
  1503. print(f"Error computing FA/MD maps: {e}")
  1504. sys.exit(1)
  1505. elif args.command == "coregister":
  1506. # Prepare arguments for coregister
  1507. coreg_args = []
  1508. for arg_name, arg_value in vars(args).items():
  1509. if arg_name != "command" and arg_value is not None:
  1510. # Convert hyphens to underscores for the script
  1511. arg_name_formatted = arg_name.replace("_", "-")
  1512. if isinstance(arg_value, bool):
  1513. if arg_value:
  1514. coreg_args.append(f"--{arg_name_formatted}")
  1515. elif isinstance(arg_value, list):
  1516. coreg_args.append(f"--{arg_name_formatted}")
  1517. coreg_args.extend([str(x) for x in arg_value])
  1518. else:
  1519. coreg_args.append(f"--{arg_name_formatted}")
  1520. coreg_args.append(str(arg_value))
  1521. # Run the coregister script
  1522. try:
  1523. print(f"Coregistering {args.moving_file} to {args.fixed_file}...")
  1524. subprocess.run(
  1525. ["python", "-m", "micaflow.scripts.coregister"] + coreg_args, check=True
  1526. )
  1527. print(f"Coregistration completed. Output saved to {args.output}")
  1528. if args.warp_file:
  1529. print(f"Warp field saved to {args.warp_file}")
  1530. if args.affine_file:
  1531. print(f"Affine transformation matrix saved to {args.affine_file}")
  1532. except subprocess.CalledProcessError as e:
  1533. print(f"Error during coregistration: {e}")
  1534. sys.exit(1)
  1535. elif args.command == "denoise":
  1536. # Prepare arguments for denoise
  1537. denoise_args = []
  1538. for arg_name, arg_value in vars(args).items():
  1539. if arg_name != "command" and arg_value is not None:
  1540. arg_name_formatted = arg_name.replace("_", "-")
  1541. if isinstance(arg_value, bool):
  1542. if arg_value:
  1543. denoise_args.append(f"--{arg_name_formatted}")
  1544. elif isinstance(arg_value, list):
  1545. denoise_args.append(f"--{arg_name_formatted}")
  1546. denoise_args.extend([str(x) for x in arg_value])
  1547. else:
  1548. denoise_args.append(f"--{arg_name_formatted}")
  1549. denoise_args.append(str(arg_value))
  1550. # Run the denoise script
  1551. try:
  1552. print(f"Denoising diffusion image {args.input}...")
  1553. subprocess.run(
  1554. ["python", "-m", "micaflow.scripts.denoise"] + denoise_args, check=True
  1555. )
  1556. print(f"Denoising completed. Output saved to {args.output}")
  1557. except subprocess.CalledProcessError as e:
  1558. print(f"Error during denoising: {e}")
  1559. sys.exit(1)
  1560. elif args.command == "motion_correction":
  1561. # Prepare arguments for motion_correction
  1562. motion_corr_args = []
  1563. for arg_name, arg_value in vars(args).items():
  1564. if arg_name != "command" and arg_value is not None:
  1565. arg_name_formatted = arg_name.replace("_", "-")
  1566. if isinstance(arg_value, bool):
  1567. if arg_value:
  1568. motion_corr_args.append(f"--{arg_name_formatted}")
  1569. elif isinstance(arg_value, list):
  1570. motion_corr_args.append(f"--{arg_name_formatted}")
  1571. motion_corr_args.extend([str(x) for x in arg_value])
  1572. else:
  1573. motion_corr_args.append(f"--{arg_name_formatted}")
  1574. motion_corr_args.append(str(arg_value))
  1575. # Run the motion_correction script
  1576. try:
  1577. print(f"Performing motion correction on {args.denoised}...")
  1578. subprocess.run(
  1579. ["python", "-m", "micaflow.scripts.motion_correction"]
  1580. + motion_corr_args,
  1581. check=True,
  1582. )
  1583. print(f"Motion correction completed. Output saved to {args.output}")
  1584. except subprocess.CalledProcessError as e:
  1585. print(f"Error during motion correction: {e}")
  1586. sys.exit(1)
  1587. elif args.command == "SDC":
  1588. # Prepare arguments for SDC
  1589. sdc_args = []
  1590. for arg_name, arg_value in vars(args).items():
  1591. if arg_name != "command" and arg_value is not None:
  1592. # Convert hyphens to underscores for the script
  1593. arg_name_formatted = arg_name.replace("_", "-")
  1594. if isinstance(arg_value, bool):
  1595. if arg_value:
  1596. sdc_args.append(f"--{arg_name_formatted}")
  1597. elif isinstance(arg_value, list):
  1598. sdc_args.append(f"--{arg_name_formatted}")
  1599. sdc_args.extend([str(x) for x in arg_value])
  1600. else:
  1601. sdc_args.append(f"--{arg_name_formatted}")
  1602. sdc_args.append(str(arg_value))
  1603. # Run the SDC script
  1604. try:
  1605. print(f"Running susceptibility distortion correction on {args.input}...")
  1606. subprocess.run(
  1607. ["python", "-m", "micaflow.scripts.SDC"] + sdc_args, check=True
  1608. )
  1609. print(
  1610. f"Susceptibility distortion correction completed. Output saved to {args.output}"
  1611. )
  1612. print(f"Warp field saved to {args.output_warp}")
  1613. except subprocess.CalledProcessError as e:
  1614. print(f"Error running susceptibility distortion correction: {e}")
  1615. sys.exit(1)
  1616. elif args.command == "texture_generation":
  1617. # Prepare arguments for texture_generation
  1618. texture_args = []
  1619. for arg_name, arg_value in vars(args).items():
  1620. if arg_name != "command" and arg_value is not None:
  1621. if isinstance(arg_value, bool):
  1622. if arg_value:
  1623. texture_args.append(f"--{arg_name}")
  1624. elif isinstance(arg_value, list):
  1625. texture_args.append(f"--{arg_name}")
  1626. texture_args.extend([str(x) for x in arg_value])
  1627. else:
  1628. texture_args.append(f"--{arg_name}")
  1629. texture_args.append(str(arg_value))
  1630. # Run the texture_generation script
  1631. try:
  1632. print(
  1633. f"Generating texture features for {args.input} using mask {args.mask}..."
  1634. )
  1635. subprocess.run(
  1636. ["python", "-m", "micaflow.scripts.texture_generation"] + texture_args,
  1637. check=True,
  1638. )
  1639. print(f"Texture generation completed. Output saved to {args.output}")
  1640. except subprocess.CalledProcessError as e:
  1641. print(f"Error during texture generation: {e}")
  1642. sys.exit(1)
  1643. elif args.command == "normalize":
  1644. # Prepare arguments for motion_correction
  1645. normalize_args = []
  1646. for arg_name, arg_value in vars(args).items():
  1647. if arg_name != "command" and arg_value is not None:
  1648. arg_name_formatted = arg_name.replace("_", "-")
  1649. if isinstance(arg_value, bool):
  1650. if arg_value:
  1651. normalize_args.append(f"--{arg_name_formatted}")
  1652. elif isinstance(arg_value, list):
  1653. normalize_args.append(f"--{arg_name_formatted}")
  1654. normalize_args.extend([str(x) for x in arg_value])
  1655. else:
  1656. normalize_args.append(f"--{arg_name_formatted}")
  1657. normalize_args.append(str(arg_value))
  1658. # Run the motion_correction script
  1659. try:
  1660. print(f"Performing motion correction on {args.input}...")
  1661. subprocess.run(
  1662. ["python", "-m", "micaflow.scripts.normalize"] + normalize_args,
  1663. check=True,
  1664. )
  1665. print(f"Motion correction completed. Output saved to {args.output}")
  1666. except subprocess.CalledProcessError as e:
  1667. print(f"Error during motion correction: {e}")
  1668. sys.exit(1)
  1669. elif args.command == "synth_b0":
  1670. # Prepare arguments for synth_b0
  1671. synth_b0_args = []
  1672. for arg_name, arg_value in vars(args).items():
  1673. if arg_name != "command" and arg_value is not None:
  1674. # Convert underscores to hyphens in argument names
  1675. arg_name_formatted = arg_name.replace("_", "-")
  1676. if isinstance(arg_value, bool):
  1677. if arg_value:
  1678. synth_b0_args.append(f"--{arg_name_formatted}")
  1679. elif isinstance(arg_value, list):
  1680. synth_b0_args.append(f"--{arg_name_formatted}")
  1681. synth_b0_args.extend([str(x) for x in arg_value])
  1682. else:
  1683. synth_b0_args.append(f"--{arg_name_formatted}")
  1684. synth_b0_args.append(str(arg_value))
  1685. # Run the synth_b0 script
  1686. try:
  1687. print(f"Generating synthetic B0 using T1w ({args.t1}) and B0 ({args.b0}) inputs...")
  1688. print(f"First performing linear registration between B0 and T1w...")
  1689. subprocess.run(
  1690. ["python", "-m", "micaflow.scripts.synth_b0"] + synth_b0_args, check=True
  1691. )
  1692. print(f"Synthetic B0 generation completed. Output saved to {args.output}")
  1693. except subprocess.CalledProcessError as e:
  1694. print(f"Error generating synthetic B0: {e}")
  1695. sys.exit(1)
  1696. elif args.command == "extract_b0":
  1697. # Prepare arguments for extract_b0
  1698. extract_b0_args = []
  1699. for arg_name, arg_value in vars(args).items():
  1700. if arg_name != "command" and arg_value is not None:
  1701. arg_name_formatted = arg_name.replace("_", "-")
  1702. if isinstance(arg_value, bool):
  1703. if arg_value:
  1704. extract_b0_args.append(f"--{arg_name_formatted}")
  1705. elif isinstance(arg_value, list):
  1706. extract_b0_args.append(f"--{arg_name_formatted}")
  1707. extract_b0_args.extend([str(x) for x in arg_value])
  1708. else:
  1709. extract_b0_args.append(f"--{arg_name_formatted}")
  1710. extract_b0_args.append(str(arg_value))
  1711. # Run the extract_b0 script
  1712. try:
  1713. print(f"Extracting b=0 volume from {args.input}...")
  1714. subprocess.run(
  1715. ["python", "-m", "micaflow.scripts.extract_b0"] + extract_b0_args,
  1716. check=True,
  1717. )
  1718. print(f"B0 extraction completed. Output saved to {args.output}")
  1719. except subprocess.CalledProcessError as e:
  1720. print(f"Error extracting b=0 volume: {e}")
  1721. sys.exit(1)
  1722. if __name__ == "__main__":
  1723. main()

cli.py at commit 51d2f73, under GPL-3.0 · at the source

Overview

  1. McConnell Brain Imaging Centre (BIC) and Centre of Excellence in Epilepsy at The Neuro (CEEN), Montreal Neurological Institute, McGill University, Montreal, Quebec, Canada
  2. Neurophysiology Unit and Epilepsy Centre, AOU Modena, University of Modena and Reggio Emilia, Modena, Italy
  3. Department of Neurosurgery, Beijing Tiantan Hospital, Capital Medical University, Beijing, China
Journal: Imaging neuroscience (Cambridge, Mass.), volume 4, article IMAG.a.1366
Dates: received 5 June 2026; accepted 17 August 2026; published online 11 September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1162/imag.a.1366 · PMID 42746345 · PMCID PMC13576946 · OpenAlex W7162771624
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), epilepsy (population), methods / tools (subfield)
Methods: Connectivity, Statistics, Preprocessing, Graphs, Machine learning, fMRI & imaging
Keywords: magnetic resonance imaging, MRI preprocessing, image processing, multimodal MRI, diffusion MRI, MRI in epilepsy
MeSH: Brain*, Image Processing, Computer-Assisted*, Magnetic Resonance Imaging*, Neuroimaging*, Deep Learning, Humans, Reproducibility of Results (* major topic)
Journal subjects: Software Toolbox
Topic: Advanced Neuroimaging Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: Irma H. Bauer Research Fund; Natural Sciences and Engineering Research Council of Canada; Helmholtz International BigBrain Analytics and Learning Laboratory; Healthy Brains and Healthy Lives; Centre for Aging + Brain Health Innovation; Molson Engineering Fellowship of the Montreal Neurological Institute; Fonds de Recherche du Québec - Santé (373207); Fonds de recherche du Québec—Nature et technologies; Savoy Foundation; Vanier Canada Graduate Scholarship; Canadian Institutes of Health Research (FDN-154298, PJT-174995); China Scholarship Council; Ministère de la Santé et des Services sociaux du Québec; Quebec BioImaging Network; Montreal Neurological Institute Jeanne Timmins Costello Fellowship; Healthy Brains, Healthy Lives – Entrepreneur Postdoc Fellowship; SickKids Foundation (NI17-039); Centre Azrieli de recherche sur l'autisme, Institut et Hôpital Neurologiques de Montréal; BrainCanada; Canada Research Chairs; Centre of Excellence in Epilepsy at the Neuro; CIHR (PJT-206196, PJT-203761, PJT-191853); NSERC (RGPIN-2025-05932)
Citations: not cited yet (Europe PMC); 89 references in the paper

Abstract

MICAFlow is a fully automated MRI preprocessing pipeline designed to support translation of advanced neuroimaging workflows toward clinically feasible research and prospective clinical-evaluation settings. The pipeline emphasizes speed, robustness, and ease of use, focusing on structural and diffusion MRI. Key innovations include a Label-Augmented Modality-Agnostic Registration (LAMAReg) technique driven by deep learning segmentations for reliable cross-modal alignment, integration of state-of-the-art distortion corrections, and adherence to reproducible standards (Snakemake workflow, BIDSApp specifications). We describe the design of MICAFlow and evaluate its performance across heterogeneous datasets. First, accessibility: MICAFlow processes a multimodal MRI exam in minutes with clinically accessible hardware and without requiring GPU access, enabling same-day processing on clinically realistic hardware. Second, registration accuracy: LAMAReg achieves cutting-edge multimodal registration accuracy, yielding accurate alignment of diffusion MRI, FLAIR, and intra-subject T1-weighted images while remaining generally robust to common artifacts. Third, data reproducibility: Using identifiability, we show MICAFlow maintains consistent performance across diverse datasets, including subjects with pathology, and is closely comparable to contemporary pipelines. In sum, MICAFlow’s combination of machine learning and efficient workflows produces research-grade preprocessing outputs with runtimes compatible with clinically oriented research workflows. This work demonstrates that advanced MRI preprocessing can be done fast and robustly, helping close the gap between research neuroimaging and future clinical evaluation of quantitative MRI techniques. The source code for MICAFlow is available here: https://github.com/MICA-MNI/MICAFlow, and for LAMAReg here: https://github.com/MICA-MNI/LAMAReg

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 17 matches between paragraphs and lines of code.

MICA-MNI/MICAFlow

License: GPL-3.0
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 51d2f73d2009809234b5ac9f5209be37a96b3cc2, 15 July 2026
Languages: Python (29), JavaScript (15)
Size: 237 files, 44 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README, license file, environment (Dockerfile, pyproject.toml, requirements.txt), tests, continuous integration, documentation
Not found: CITATION.cff
Tools: NumPy (13 files), NiBabel (11 files), ANTs (8 files), PyTorch (4 files), SciPy (4 files), DIPY (3 files), Nilearn (1 file), Snakemake (1 file), TensorFlow (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
46 files

mica-mni.github.io/micaflow

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Data and Code Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
  • 26 September 2026: the link answers (HTTP 200)

MICA-MNI/MICAFlow-Models

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 581e9fbaf0390341654483a100e14ef4fd9c263d, 10 March 2026
Size: 28 files, 0 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers

MICA-MNI/LAMAReg

License: MIT
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 78b3c97d4d31b49c9cafb9d393c351955c7f3b27, 15 July 2026
Languages: Python (46)
Size: 232 files, 46 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README, license file, environment (pyproject.toml, requirements.txt), tests, continuous integration, documentation
Not found: CITATION.cff
Tools: NumPy (30 files), TensorFlow (23 files), Keras (21 files), SciPy (6 files), ANTs (4 files), NiBabel (4 files), Matplotlib (2 files), FreeSurfer (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
48 files

MICA-MNI/LAMAR-Models

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 5f43003b01e56d2754b4c0e23a98f38d1d134de3, 10 April 2025
Size: 9 files, 0 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 5 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 90 scripts, each with its path and the digest of its content;
  • 17 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and Code Availability

MICAFlow is available as an open-source Python package. The full source code, version history, and issue tracker are hosted on GitHub https://github.com/MICA-MNI/MICAFlow, with documentation maintained on ReadTheDocs https://mica-mni.github.io/MICAFlow. Command-line usage and options are documented via the built-in help interface (i.e., micaflow —help). The package is also distributed via PyPI https://pypi.org/project/MICAFlow/ and can be installed with “pip install micaflow” on systems with a supported Python environment (≥3.10). Models for Synb0-DISCO and MNI152 atlases are downloaded at runtime (repository link: https://github.com/MICA-MNI/MICAFlow-Models).

LAMAReg is additionally released as a standalone open-source package, with source code hosted on GitHub https://github.com/MICA-MNI/LAMAReg and distribution via PyPI https://pypi.org/project/LAMAReg. Command-line usage and options are also documented via the built-in help interface (e.g., lamareg —help). LAMAReg uses the original SynthSeg model weights; for lightweight installation, the weights are hosted as a set of packaged components that are downloaded and reassembled at runtime (https://github.com/MICA-MNI/LAMAR-Models).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 22 authors, 6 keywords, 7 MeSH terms, 23 funders, 88 references.

Cite

This paper

Goodall-Halliwell, I., DeKraker, J., Bautin, P., Mendelson, D., Cabalo, D. G., Sahlas, E., Ngo, A., Xie, K., Lam, J., Smith, M., Hwang, Y., Vavassori, L., Milano, P., Chen, J., Dascal, A., Ding, R., Zhou, G., Naish, M., Mo, J., . . . Bernhardt, B. C. (2026). MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice. Imaging neuroscience (Cambridge, Mass.), 4, IMAG.a.1366. https://doi.org/10.1162/imag.a.1366

BibTeX

@article{goodallhalliwell2026micaflow,
author = {Goodall-Halliwell, Ian and DeKraker, Jordan and Bautin, Paul and Mendelson, Daniel and Cabalo, Donna Gift and Sahlas, Ella and Ngo, Alexander and Xie, Ke and Lam, Jack and Smith, Meaghan and Hwang, Youngeun and Vavassori, Laura and Milano, Pietro and Chen, Judy and Dascal, Arielle and Ding, Rui and Zhou, Guan and Naish, Marlo and Mo, JiaJie and Fadaie, Fatemeh and Cruces, Raul R. and Bernhardt, Boris C.},
title = {{MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice}},
journal = {Imaging neuroscience (Cambridge, Mass.)},
year = {2026},
month = sep,
volume = {4},
pages = {IMAG.a.1366},
publisher = {MIT Press},
issn = {2837-6056},
doi = {10.1162/imag.a.1366},
url = {https://doi.org/10.1162/imag.a.1366},
pmid = {42746345},
pmcid = {PMC13576946}
}

RIS

TY - JOUR
AU - Goodall-Halliwell, Ian
AU - DeKraker, Jordan
AU - Bautin, Paul
AU - Mendelson, Daniel
AU - Cabalo, Donna Gift
AU - Sahlas, Ella
AU - Ngo, Alexander
AU - Xie, Ke
AU - Lam, Jack
AU - Smith, Meaghan
AU - Hwang, Youngeun
AU - Vavassori, Laura
AU - Milano, Pietro
AU - Chen, Judy
AU - Dascal, Arielle
AU - Ding, Rui
AU - Zhou, Guan
AU - Naish, Marlo
AU - Mo, JiaJie
AU - Fadaie, Fatemeh
AU - Cruces, Raul R.
AU - Bernhardt, Boris C.
TI - MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice
T2 - Imaging neuroscience (Cambridge, Mass.)
J2 - Imaging Neurosci (Camb)
PY - 2026
DA - 2026/09/11
VL - 4
SP - IMAG.a.1366
SN - 2837-6056
PB - MIT Press
DO - 10.1162/imag.a.1366
UR - https://doi.org/10.1162/imag.a.1366
LA - en
ER -

CSL-JSON

{
"id": "10.1162/imag.a.1366",
"type": "article-journal",
"title": "MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice",
"container-title": "Imaging neuroscience (Cambridge, Mass.)",
"author": [
{
"family": "Goodall-Halliwell",
"given": "Ian"
},
{
"family": "DeKraker",
"given": "Jordan"
},
{
"family": "Bautin",
"given": "Paul"
},
{
"family": "Mendelson",
"given": "Daniel"
},
{
"family": "Cabalo",
"given": "Donna Gift"
},
{
"family": "Sahlas",
"given": "Ella"
},
{
"family": "Ngo",
"given": "Alexander"
},
{
"family": "Xie",
"given": "Ke"
},
{
"family": "Lam",
"given": "Jack"
},
{
"family": "Smith",
"given": "Meaghan"
},
{
"family": "Hwang",
"given": "Youngeun"
},
{
"family": "Vavassori",
"given": "Laura"
},
{
"family": "Milano",
"given": "Pietro"
},
{
"family": "Chen",
"given": "Judy"
},
{
"family": "Dascal",
"given": "Arielle"
},
{
"family": "Ding",
"given": "Rui"
},
{
"family": "Zhou",
"given": "Guan"
},
{
"family": "Naish",
"given": "Marlo"
},
{
"family": "Mo",
"given": "JiaJie"
},
{
"family": "Fadaie",
"given": "Fatemeh"
},
{
"family": "Cruces",
"given": "Raul R."
},
{
"family": "Bernhardt",
"given": "Boris C."
}
],
"container-title-short": "Imaging Neurosci (Camb)",
"volume": "4",
"page": "IMAG.a.1366",
"DOI": "10.1162/imag.a.1366",
"PMID": "42746345",
"PMCID": "PMC13576946",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://doi.org/10.1162/imag.a.1366",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
11
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1002/epi.70254
Use of artificial intelligence in magnetic resonance imaging across the epileptic patient's journey: A meta-analysis of four clinical applications.
Journal: Epilepsia
In common: epilepsy, structural MRI / diffusion, 1 reference, 3 authors
[2] doi:10.1073/pnas.2604111123 [code]
Multiscale characterization of the human claustrum from histology to MRI.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: ANTs, FreeSurfer, structural MRI / diffusion, 3 references, 2 authors
[3] doi:10.64898/2026.04.02.26349812 [code]
9.4 Tesla MRI in focal epilepsy patients with high-resolution surface-based profiling of focal cortical dysplasias
Journal: medRxiv (preprint)
In common: ANTs, FreeSurfer, Nilearn, 4 other tools, epilepsy, structural MRI / diffusion, 4 references
[4] doi:10.2463/mrms.mp.2024-0149 [code]
Image Distortion Correction for Diffusion MR Imaging Using a Transformer-based U-Net.
Journal: Magnetic resonance in medical sciences : MRMS : an official journal of Japan Society of Magnetic Resonance in Medicine
In common: ANTs, FreeSurfer, NiBabel, 4 other tools, structural MRI / diffusion, 4 references
[5] doi:10.1002/nbm.70353 [code]
Automated Surface-Based Segmentation of Deep Gray Matter Regions Based on Diffusion Tensor Images Reveals Unique Age Trajectories Over the Healthy Lifespan.
Journal: NMR in biomedicine
In common: DIPY, ANTs, FreeSurfer, 3 other tools, structural MRI / diffusion, 5 references
[6] doi:10.7554/elife.108408 [code]
Frequency and laminar profile of feature-specific visual activity revealed by interleaved EEG-fMRI.
Journal: eLife
In common: DIPY, ANTs, FreeSurfer, 4 other tools, 5 references
[7] doi:10.1371/journal.pbio.3003755 [code]
Action information is integrated into entorhinal representations of conceptual space and is reflected in eye movements.
Journal: PLoS biology
In common: ANTs, TensorFlow, Nilearn, 4 other tools, 5 references
[8] doi:10.1162/imag.a.1362 [code]
Human fMRI at 11.7T: Assessing feasibility, stability, and reliability on the Iseult scanner.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: ANTs, FreeSurfer, Nilearn, 4 other tools, methods / tools, 4 references
[9] doi:10.64898/2026.08.18.26360725 [code]
Temporal pole blurring in hippocampal sclerosis reflects seizure-disrupted myelination
Journal: medRxiv (preprint)
In common: FreeSurfer, Nilearn, NiBabel, 4 other tools, epilepsy, structural MRI / diffusion, 3 references
[10] doi:10.1038/s41467-026-71555-0 [code]
A deep representation learning model to predict response to vagus nerve stimulation.
Journal: Nature communications
In common: ANTs, FreeSurfer, NiBabel, 4 other tools, epilepsy, structural MRI / diffusion, 4 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.