Skip to content

qsiprep

tit.pre.qsi.qsiprep

QSIPrep runner for TI-Toolbox.

This module provides functions to run QSIPrep as a sibling Docker container using the Docker-out-of-Docker (DooD) pattern.

run_qsiprep

run_qsiprep(project_dir: str, subject_id: str, *, logger: Logger, output_resolution: float = QSI_DEFAULT_OUTPUT_RESOLUTION, cpus: int | None = None, memory_gb: int | None = None, omp_threads: int = QSI_DEFAULT_OMP_THREADS, image_tag: str = QSI_DEFAULT_IMAGE_TAG, skip_bids_validation: bool = True, denoise_method: str = 'dwidenoise', unringing_method: str = 'mrdegibbs', runner: CommandRunner | None = None) -> None

Run QSIPrep preprocessing for a subject's DWI data.

This function spawns a QSIPrep Docker container as a sibling to the current SimNIBS container using Docker-out-of-Docker (DooD).

Parameters

project_dir : str Path to the BIDS project root directory. subject_id : str Subject identifier (without 'sub-' prefix). logger : logging.Logger Logger for status messages. output_resolution : float, optional Target output resolution in mm. Default: 2.0. cpus : int, optional Number of CPUs to allocate. Default: 8. memory_gb : int, optional Memory limit in GB. Default: 32. omp_threads : int, optional Number of OpenMP threads. Default: 1. image_tag : str, optional QSIPrep Docker image tag. Default: '1.1.1'. skip_bids_validation : bool, optional Skip BIDS validation. Default: True. denoise_method : str, optional Denoising method. Default: 'dwidenoise'. unringing_method : str, optional Unringing method. Default: 'mrdegibbs'. runner : CommandRunner | None, optional Command runner for subprocess execution.

Raises

PreprocessError If QSIPrep fails or prerequisites are not met.

Source code in tit/pre/qsi/qsiprep.py
def run_qsiprep(
    project_dir: str,
    subject_id: str,
    *,
    logger: logging.Logger,
    output_resolution: float = const.QSI_DEFAULT_OUTPUT_RESOLUTION,
    cpus: int | None = None,
    memory_gb: int | None = None,
    omp_threads: int = const.QSI_DEFAULT_OMP_THREADS,
    image_tag: str = const.QSI_DEFAULT_IMAGE_TAG,
    skip_bids_validation: bool = True,
    denoise_method: str = "dwidenoise",
    unringing_method: str = "mrdegibbs",
    runner: CommandRunner | None = None,
) -> None:
    """
    Run QSIPrep preprocessing for a subject's DWI data.

    This function spawns a QSIPrep Docker container as a sibling to the
    current SimNIBS container using Docker-out-of-Docker (DooD).

    Parameters
    ----------
    project_dir : str
        Path to the BIDS project root directory.
    subject_id : str
        Subject identifier (without 'sub-' prefix).
    logger : logging.Logger
        Logger for status messages.
    output_resolution : float, optional
        Target output resolution in mm. Default: 2.0.
    cpus : int, optional
        Number of CPUs to allocate. Default: 8.
    memory_gb : int, optional
        Memory limit in GB. Default: 32.
    omp_threads : int, optional
        Number of OpenMP threads. Default: 1.
    image_tag : str, optional
        QSIPrep Docker image tag. Default: '1.1.1'.
    skip_bids_validation : bool, optional
        Skip BIDS validation. Default: True.
    denoise_method : str, optional
        Denoising method. Default: 'dwidenoise'.
    unringing_method : str, optional
        Unringing method. Default: 'mrdegibbs'.
    runner : CommandRunner | None, optional
        Command runner for subprocess execution.

    Raises
    ------
    PreprocessError
        If QSIPrep fails or prerequisites are not met.
    """
    logger.info(f"Starting QSIPrep for subject {subject_id}")

    # Validate DWI data exists
    is_valid, error_msg = validate_bids_dwi(project_dir, subject_id, logger)
    if not is_valid:
        raise PreprocessError(f"DWI validation failed: {error_msg}")

    # Check for existing output
    output_dir = Path(project_dir) / "derivatives" / "qsiprep" / f"sub-{subject_id}"

    if output_dir.exists():
        existing_valid, _ = validate_qsiprep_output(project_dir, subject_id)
        if existing_valid:
            raise PreprocessError(
                f"QSIPrep output already exists at {output_dir}. "
                "Remove the directory manually before rerunning."
            )

    # Create output directories
    output_dir.parent.mkdir(parents=True, exist_ok=True)
    work_dir = Path(project_dir) / "derivatives" / ".qsiprep_work"
    work_dir.mkdir(parents=True, exist_ok=True)

    # Build configuration
    config = QSIPrepConfig(
        subject_id=subject_id,
        output_resolution=output_resolution,
        resources=ResourceConfig(
            cpus=cpus,
            memory_gb=memory_gb,
            omp_threads=omp_threads,
        ),
        image_tag=image_tag,
        skip_bids_validation=skip_bids_validation,
        denoise_method=denoise_method,
        unringing_method=unringing_method,
    )

    try:
        # Build Docker command
        builder = DockerCommandBuilder(project_dir)
        cmd = builder.build_qsiprep_cmd(config)
    except DockerBuildError as e:
        raise PreprocessError(f"Failed to build QSIPrep command: {e}")

    # Ensure image is available
    if not pull_image_if_needed(const.QSI_QSIPREP_IMAGE, image_tag, logger):
        raise PreprocessError(
            f"Failed to pull QSIPrep image: {const.QSI_QSIPREP_IMAGE}:{image_tag}"
        )

    # Log the command for debugging
    logger.debug(f"QSIPrep command: {' '.join(cmd)}")

    # Run the container
    if runner is None:
        runner = CommandRunner()

    logger.info(f"Running QSIPrep for subject {subject_id}...")
    returncode = runner.run(cmd, logger=logger)

    if returncode != 0:
        raise PreprocessError(f"QSIPrep failed with exit code {returncode}")

    # Validate output
    is_valid, error_msg = validate_qsiprep_output(project_dir, subject_id)
    if not is_valid:
        raise PreprocessError(f"QSIPrep output validation failed: {error_msg}")

    logger.info(f"QSIPrep completed successfully for subject {subject_id}")