Skip to content

qsi

tit.pre.qsi

QSI (QSIPrep/QSIRecon) Integration for TI-Toolbox.

This module provides Docker-out-of-Docker (DooD) integration for running QSIPrep and QSIRecon as sibling containers from within the SimNIBS container.

The primary use case is preprocessing DWI data to extract DTI tensors for SimNIBS anisotropic conductivity simulations.

Public API
  • run_qsiprep: Run QSIPrep preprocessing pipeline
  • run_qsirecon: Run QSIRecon reconstruction pipeline
  • extract_dti_tensor: Extract DTI tensor for SimNIBS integration
Configuration Classes
  • QSIPrepConfig: Configuration for QSIPrep runs
  • QSIReconConfig: Configuration for QSIRecon runs
  • ReconSpec: Enum of available reconstruction specifications
  • QSIAtlas: Enum of available atlases for connectivity analysis

QSIPrepConfig dataclass

QSIPrepConfig(subject_id: str, output_resolution: float = QSI_DEFAULT_OUTPUT_RESOLUTION, resources: ResourceConfig = ResourceConfig(), image_tag: str = QSI_DEFAULT_IMAGE_TAG, skip_bids_validation: bool = True, denoise_method: str = 'dwidenoise', unringing_method: str = 'mrdegibbs', distortion_group_merge: str = 'none')

Configuration for a QSIPrep run.

Attributes

subject_id : str Subject identifier (without 'sub-' prefix). output_resolution : float Target output resolution in mm (default: 2.0). resources : ResourceConfig Resource allocation settings. image_tag : str Docker image tag for QSIPrep. skip_bids_validation : bool Whether to skip BIDS validation (useful for non-BIDS datasets). denoise_method : str Denoising method: 'dwidenoise', 'patch2self', or 'none'. unringing_method : str Unringing method: 'mrdegibbs', 'rpg', or 'none'. distortion_group_merge : str Method for merging distortion groups: 'concatenate', 'average', or 'none'.

QSIReconConfig dataclass

QSIReconConfig(subject_id: str, recon_specs: list[str] = (lambda: ['mrtrix_multishell_msmt_ACT-fast'])(), atlases: list[str] | None = (lambda: ['Schaefer100', 'AAL116'])(), use_gpu: bool = False, resources: ResourceConfig = ResourceConfig(), image_tag: str = QSI_DEFAULT_IMAGE_TAG, skip_odf_reports: bool = True)

Configuration for a QSIRecon run.

Attributes

subject_id : str Subject identifier (without 'sub-' prefix). recon_specs : list[str] List of reconstruction specs to run. Defaults to ['dipy_dki']. atlases : list[str] | None List of atlases for connectivity analysis. None = no connectivity. use_gpu : bool Whether to enable GPU acceleration (requires NVIDIA Docker runtime). resources : ResourceConfig Resource allocation settings. image_tag : str Docker image tag for QSIRecon. skip_odf_reports : bool Whether to skip ODF report generation (saves time).

ReconSpec

Bases: StrEnum

Available QSIRecon reconstruction specifications.

Each spec defines a complete reconstruction pipeline with specific algorithms and output formats.

from_string classmethod

from_string(value: str) -> Self

Convert string to ReconSpec enum.

Source code in tit/pre/qsi/config.py
@classmethod
def from_string(cls, value: str) -> Self:
    """Convert string to ReconSpec enum."""
    for spec in cls:
        if spec.value == value:
            return spec
    raise ValueError(f"Unknown recon spec: {value}")

list_all classmethod

list_all() -> list[str]

Return list of all spec values.

Source code in tit/pre/qsi/config.py
@classmethod
def list_all(cls) -> list[str]:
    """Return list of all spec values."""
    return [spec.value for spec in cls]

QSIAtlas

Bases: StrEnum

Available atlases for QSIRecon connectivity analysis.

These atlases can be used for structural connectivity matrix generation.

from_string classmethod

from_string(value: str) -> Self

Convert string to QSIAtlas enum.

Source code in tit/pre/qsi/config.py
@classmethod
def from_string(cls, value: str) -> Self:
    """Convert string to QSIAtlas enum."""
    for atlas in cls:
        if atlas.value == value:
            return atlas
    raise ValueError(f"Unknown atlas: {value}")

list_all classmethod

list_all() -> list[str]

Return list of all atlas values.

Source code in tit/pre/qsi/config.py
@classmethod
def list_all(cls) -> list[str]:
    """Return list of all atlas values."""
    return [atlas.value for atlas in cls]

ResourceConfig dataclass

ResourceConfig(cpus: int | None = None, memory_gb: int | None = None, omp_threads: int = QSI_DEFAULT_OMP_THREADS)

Resource allocation configuration for QSI containers.

Attributes

cpus : int Number of CPUs to allocate to the container. memory_gb : int Memory limit in gigabytes. omp_threads : int Number of OpenMP threads (affects ANTS, MRtrix, etc.).

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}")

run_qsirecon

run_qsirecon(project_dir: str, subject_id: str, *, logger: Logger, recon_specs: list[str] | None = None, atlases: list[str] | None = None, use_gpu: bool = False, cpus: int | None = None, memory_gb: int | None = None, omp_threads: int = QSI_DEFAULT_OMP_THREADS, image_tag: str = QSI_DEFAULT_IMAGE_TAG, skip_odf_reports: bool = True, runner: CommandRunner | None = None) -> None

Run QSIRecon reconstruction for a subject's preprocessed DWI data.

This function spawns QSIRecon Docker containers as siblings to the current SimNIBS container using Docker-out-of-Docker (DooD).

QSIRecon requires QSIPrep output as input. Multiple reconstruction specs can be run sequentially.

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. recon_specs : list[str] | None, optional List of reconstruction specifications to run. Default: ['mrtrix_multishell_msmt_ACT-fast']. Available specs: mrtrix_multishell_msmt_ACT-fast, multishell_scalarfest, dipy_dki, dipy_mapmri, amico_noddi, pyafq_tractometry, etc. atlases : list[str] | None, optional List of atlases for connectivity analysis. Default: ['Schaefer100', 'AAL116']. use_gpu : bool, optional Enable GPU acceleration. Default: False. 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 QSIRecon Docker image tag. Default: '1.1.1'. skip_odf_reports : bool, optional Skip ODF report generation. Default: True. runner : CommandRunner | None, optional Command runner for subprocess execution.

Raises

PreprocessError If QSIRecon fails or prerequisites are not met.

Source code in tit/pre/qsi/qsirecon.py
def run_qsirecon(
    project_dir: str,
    subject_id: str,
    *,
    logger: logging.Logger,
    recon_specs: list[str] | None = None,
    atlases: list[str] | None = None,
    use_gpu: bool = False,
    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_odf_reports: bool = True,
    runner: CommandRunner | None = None,
) -> None:
    """
    Run QSIRecon reconstruction for a subject's preprocessed DWI data.

    This function spawns QSIRecon Docker containers as siblings to the
    current SimNIBS container using Docker-out-of-Docker (DooD).

    QSIRecon requires QSIPrep output as input. Multiple reconstruction
    specs can be run sequentially.

    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.
    recon_specs : list[str] | None, optional
        List of reconstruction specifications to run. Default: ['mrtrix_multishell_msmt_ACT-fast'].
        Available specs: mrtrix_multishell_msmt_ACT-fast, multishell_scalarfest,
        dipy_dki, dipy_mapmri, amico_noddi, pyafq_tractometry, etc.
    atlases : list[str] | None, optional
        List of atlases for connectivity analysis. Default: ['Schaefer100', 'AAL116'].
    use_gpu : bool, optional
        Enable GPU acceleration. Default: False.
    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
        QSIRecon Docker image tag. Default: '1.1.1'.
    skip_odf_reports : bool, optional
        Skip ODF report generation. Default: True.
    runner : CommandRunner | None, optional
        Command runner for subprocess execution.

    Raises
    ------
    PreprocessError
        If QSIRecon fails or prerequisites are not met.
    """
    # Default to mrtrix_multishell_msmt_ACT-fast if no specs provided
    if recon_specs is None:
        recon_specs = ["mrtrix_multishell_msmt_ACT-fast"]

    # Default atlases for connectivity-based recon specs
    if atlases is None:
        atlases = ["Schaefer100", "AAL116"]

    logger.info(
        f"Starting QSIRecon for subject {subject_id} with specs: {recon_specs}, atlases: {atlases}"
    )

    # Validate QSIPrep output exists
    is_valid, error_msg = validate_qsiprep_output(project_dir, subject_id)
    if not is_valid:
        raise PreprocessError(
            f"QSIPrep output validation failed: {error_msg}. "
            "Run QSIPrep first before running QSIRecon."
        )

    # Create output directories
    output_base = Path(project_dir) / "derivatives" / "qsirecon"
    output_base.mkdir(parents=True, exist_ok=True)
    work_dir = Path(project_dir) / "derivatives" / ".qsirecon_work"
    work_dir.mkdir(parents=True, exist_ok=True)

    # Build configuration
    config = QSIReconConfig(
        subject_id=subject_id,
        recon_specs=recon_specs,
        atlases=atlases,
        use_gpu=use_gpu,
        resources=ResourceConfig(
            cpus=cpus,
            memory_gb=memory_gb,
            omp_threads=omp_threads,
        ),
        image_tag=image_tag,
        skip_odf_reports=skip_odf_reports,
    )

    try:
        # Build Docker command builder
        builder = DockerCommandBuilder(project_dir)
    except DockerBuildError as e:
        raise PreprocessError(f"Failed to initialize Docker: {e}")

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

    # Create runner if not provided
    if runner is None:
        runner = CommandRunner()

    # Run each recon spec
    for spec in recon_specs:
        logger.info(f"Running QSIRecon spec: {spec}")

        # Check for existing output for this spec
        # QSIRecon outputs are organized by recon spec
        spec_output_dir = output_base / f"sub-{subject_id}"

        if spec_output_dir.exists():
            raise PreprocessError(
                f"QSIRecon output already exists at {spec_output_dir}. "
                "Remove the directory manually before rerunning."
            )

        try:
            cmd = builder.build_qsirecon_cmd(config, spec)
        except DockerBuildError as e:
            raise PreprocessError(f"Failed to build QSIRecon command: {e}")

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

        # Run the container
        logger.info(f"Running QSIRecon {spec} for subject {subject_id}...")
        returncode = runner.run(cmd, logger=logger)

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

        logger.info(f"QSIRecon {spec} completed for subject {subject_id}")

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

extract_dti_tensor

extract_dti_tensor(project_dir: str, subject_id: str, *, logger: Logger, source: str = 'qsirecon', skip_registration: bool = False) -> Path

Extract DTI tensor from QSIRecon output for SimNIBS.

This function finds the DTI tensor in QSIRecon outputs, converts it to SimNIBS format, and saves it to the m2m directory.

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. source : str, optional Source of DTI tensor. Currently only 'qsirecon' is supported. Default: 'qsirecon'. skip_registration : bool, optional If True, skip registration to SimNIBS T1 space. Use this if the tensor is already in the correct space. Default: False.

Returns

Path Path to the extracted DTI tensor file in m2m directory.

Raises

PreprocessError If tensor extraction fails.

Source code in tit/pre/qsi/dti_extractor.py
def extract_dti_tensor(
    project_dir: str,
    subject_id: str,
    *,
    logger: logging.Logger,
    source: str = "qsirecon",
    skip_registration: bool = False,
) -> Path:
    """
    Extract DTI tensor from QSIRecon output for SimNIBS.

    This function finds the DTI tensor in QSIRecon outputs, converts it
    to SimNIBS format, and saves it to the m2m directory.

    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.
    source : str, optional
        Source of DTI tensor. Currently only 'qsirecon' is supported.
        Default: 'qsirecon'.
    skip_registration : bool, optional
        If True, skip registration to SimNIBS T1 space. Use this if the
        tensor is already in the correct space. Default: False.

    Returns
    -------
    Path
        Path to the extracted DTI tensor file in m2m directory.

    Raises
    ------
    PreprocessError
        If tensor extraction fails.
    """
    # Delayed import to avoid circular dependencies
    import nibabel as nib

    logger.info(f"Extracting DTI tensor for subject {subject_id}")

    # Get paths
    pm = get_path_manager(project_dir)

    m2m_dir = pm.m2m(subject_id)
    if not os.path.isdir(m2m_dir):
        raise PreprocessError(
            f"m2m directory not found for subject {subject_id}. "
            "Run SimNIBS charm first."
        )

    output_path = Path(m2m_dir) / const.FILE_DTI_TENSOR

    # Check for existing output
    if output_path.exists():
        raise PreprocessError(
            f"DTI tensor already exists at {output_path}. "
            "Remove the file manually before rerunning."
        )

    # Find source tensor
    if source != "qsirecon":
        raise PreprocessError(f"Unknown source: {source}")

    qsirecon_dir = Path(project_dir) / "derivatives" / "qsirecon"

    # Try to find DSI Studio tensor components first (most common)
    dsistudio_components = _find_dsistudio_tensor_components(
        qsirecon_dir, subject_id, logger
    )

    if dsistudio_components:
        logger.info("Using DSI Studio tensor components")
        tensor_data, affine, header = _combine_dsistudio_tensor_components(
            dsistudio_components, logger
        )
        # Already in the correct format [Dxx, Dxy, Dxz, Dyy, Dyz, Dzz]
        simnibs_tensor = tensor_data
    else:
        # Try to find DKI tensor
        dt_file, _ = _find_dki_tensor_files(qsirecon_dir, subject_id, logger)

        if dt_file is not None:
            source_file = dt_file
            logger.info(f"Using DKI diffusion tensor: {dt_file}")
        else:
            # Fall back to general DTI tensor search
            source_file = _find_dti_tensor_file(qsirecon_dir, subject_id, logger)

        if source_file is None:
            raise PreprocessError(
                f"No DTI tensor found for subject {subject_id} in QSIRecon output. "
                "Ensure QSIRecon was run with DSI Studio (dsi_studio_gqi) or "
                "another DTI-producing spec like dipy_dki."
            )

        logger.info(f"Source tensor file: {source_file}")

        # Load and convert tensor
        try:
            tensor_img = nib.load(str(source_file))
            tensor_data = tensor_img.get_fdata(dtype=np.float32)
            affine = tensor_img.affine
            header = tensor_img.header
        except (OSError, ValueError) as e:
            raise PreprocessError(f"Failed to load tensor file: {e}")

        # Convert to SimNIBS format
        try:
            simnibs_tensor = _convert_tensor_to_simnibs_format(tensor_data, logger)
        except ValueError as e:
            raise PreprocessError(f"Failed to convert tensor: {e}")

    _validate_tensor(simnibs_tensor, logger)

    # Save intermediate tensor (before registration)
    intermediate_path = Path(m2m_dir) / "DTI_ACPC_tensor.nii.gz"
    try:
        intermediate_img = nib.Nifti1Image(simnibs_tensor, affine, header)
        nib.save(intermediate_img, str(intermediate_path))
        logger.info(f"Saved intermediate tensor to: {intermediate_path}")
    except (OSError, ValueError, TypeError) as e:
        raise PreprocessError(f"Failed to save intermediate tensor: {e}")

    # Register to SimNIBS T1 space
    if not skip_registration:
        simnibs_t1_path = Path(m2m_dir) / const.FILE_T1
        if not simnibs_t1_path.exists():
            raise PreprocessError(
                f"SimNIBS T1 not found at {simnibs_t1_path}. "
                "Run SimNIBS charm first."
            )

        qsiprep_t1_path = _find_qsiprep_t1(Path(project_dir), subject_id)
        if qsiprep_t1_path is None:
            logger.warning(
                "qsiprep T1 not found. Using simple resampling instead of "
                "proper registration."
            )
            _resample_tensor_to_target(
                intermediate_path, simnibs_t1_path, output_path, logger
            )
        else:
            try:
                _register_tensor_to_simnibs_t1(
                    intermediate_path,
                    qsiprep_t1_path,
                    simnibs_t1_path,
                    output_path,
                    logger,
                )
            except PreprocessError as e:
                logger.warning(
                    f"Registration failed: {e}. Falling back to simple resampling."
                )
                _resample_tensor_to_target(
                    intermediate_path, simnibs_t1_path, output_path, logger
                )
    else:
        # Just copy the intermediate file to output
        shutil.copy2(intermediate_path, output_path)
        logger.info(f"Copied tensor to output (skip_registration=True)")

    logger.info(f"Saved DTI tensor to: {output_path}")
    return output_path

check_dti_tensor_exists

check_dti_tensor_exists(project_dir: str, subject_id: str) -> bool

Check if a DTI tensor file exists for a subject.

Parameters

project_dir : str Path to the project directory. subject_id : str Subject identifier.

Returns

bool True if the DTI tensor file exists in the m2m directory.

Source code in tit/pre/qsi/dti_extractor.py
def check_dti_tensor_exists(project_dir: str, subject_id: str) -> bool:
    """
    Check if a DTI tensor file exists for a subject.

    Parameters
    ----------
    project_dir : str
        Path to the project directory.
    subject_id : str
        Subject identifier.

    Returns
    -------
    bool
        True if the DTI tensor file exists in the m2m directory.
    """
    pm = get_path_manager(project_dir)

    m2m_dir = pm.m2m(subject_id)
    if not os.path.isdir(m2m_dir):
        return False

    tensor_path = Path(m2m_dir) / const.FILE_DTI_TENSOR
    return tensor_path.exists()