Skip to content

dti_extractor

tit.pre.qsi.dti_extractor

DTI tensor extraction for SimNIBS integration.

Extracts DTI tensors from QSIRecon DSI Studio GQI output, registers them to the SimNIBS T1 grid, and pre-compensates for SimNIBS's correct_FSL so that world-space conductivity tensors are correct.

SimNIBS expects DTI_coregT1_tensor.nii.gz — a 4D NIfTI (X, Y, Z, 6) in FSL upper-triangular order: [Dxx, Dxy, Dxz, Dyy, Dyz, Dzz].

QSIRecon DSI Studio GQI output (BIDS-compliant): derivatives/qsirecon/derivatives/qsirecon-DSIStudio/sub-{id}/dwi/ sub-{id}_space-ACPC_model-tensor_param-{txx,...,tzz}_dwimap.nii.gz

extract_dti_tensor

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

Extract and register a DTI tensor from QSIRecon DSI Studio output.

Loads the six tensor components produced by DSI Studio GQI, validates them, registers the tensor to the SimNIBS T1 grid (with FSL-convention pre-compensation), saves DTI_coregT1_tensor.nii.gz into the m2m directory, and generates a QC report.

Parameters

project_dir : str BIDS project root directory. subject_id : str Subject identifier (e.g. '070'). logger : logging.Logger Logger instance for progress and diagnostic messages. skip_registration : bool, optional When True, copy the tensor as-is without resampling or reorientation. Default is False.

Returns

pathlib.Path Path to the saved DTI_coregT1_tensor.nii.gz.

Raises

tit.pre.utils.PreprocessError If required inputs are missing, the tensor already exists, or the tensor data is invalid.

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

    Loads the six tensor components produced by DSI Studio GQI,
    validates them, registers the tensor to the SimNIBS T1 grid
    (with FSL-convention pre-compensation), saves
    ``DTI_coregT1_tensor.nii.gz`` into the m2m directory, and
    generates a QC report.

    Parameters
    ----------
    project_dir : str
        BIDS project root directory.
    subject_id : str
        Subject identifier (e.g. ``'070'``).
    logger : logging.Logger
        Logger instance for progress and diagnostic messages.
    skip_registration : bool, optional
        When *True*, copy the tensor as-is without resampling or
        reorientation.  Default is *False*.

    Returns
    -------
    pathlib.Path
        Path to the saved ``DTI_coregT1_tensor.nii.gz``.

    Raises
    ------
    tit.pre.utils.PreprocessError
        If required inputs are missing, the tensor already exists, or
        the tensor data is invalid.
    """
    project = Path(project_dir)
    logger.info(f"Extracting DTI tensor for subject {subject_id}")

    pm = get_path_manager(project_dir)
    m2m_dir = Path(pm.m2m(subject_id))
    if not m2m_dir.is_dir():
        raise PreprocessError(f"m2m directory not found: {m2m_dir}. Run charm first.")

    output_path = m2m_dir / const.FILE_DTI_TENSOR
    if output_path.exists():
        raise PreprocessError(
            f"DTI tensor already exists at {output_path}. "
            "Remove the file before rerunning."
        )

    simnibs_t1 = m2m_dir / const.FILE_T1
    if not simnibs_t1.exists():
        raise PreprocessError(f"SimNIBS T1 not found: {simnibs_t1}. Run charm first.")

    dwi_dir = _dsistudio_dwi_dir(project, subject_id)
    if not dwi_dir.is_dir():
        raise PreprocessError(
            f"DSI Studio output not found: {dwi_dir}. "
            "Run QSIRecon with dsi_studio_gqi first."
        )

    # Load and validate
    tensor_data, affine = _load_tensor(dwi_dir, subject_id, logger)
    _validate_tensor(tensor_data, logger)

    # Save intermediate in ACPC space
    intermediate = m2m_dir / "DTI_ACPC_tensor.nii.gz"
    _save_nifti_gz(tensor_data, affine, intermediate, logger)
    logger.info(f"Intermediate tensor: {intermediate}")

    # Register to SimNIBS T1 space
    if skip_registration:
        shutil.copy2(intermediate, output_path)
        logger.info("Copied tensor as-is (skip_registration=True)")
    else:
        acpc_t1 = _qsiprep_t1(project, subject_id)
        _register_tensor(tensor_data, affine, simnibs_t1, acpc_t1, output_path, logger)

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

    # QC report
    from tit.reporting.generators.dti_qc import create_dti_qc_report

    qc_path = create_dti_qc_report(
        project_dir=project_dir,
        subject_id=subject_id,
        tensor_file=str(output_path),
        t1_file=str(simnibs_t1),
    )
    logger.info(f"DTI QC report: {qc_path}")

    return output_path

check_dti_tensor_exists

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

Check whether a registered DTI tensor already exists for subject_id.

Parameters

project_dir : str BIDS project root directory. subject_id : str Subject identifier.

Returns

bool True if DTI_coregT1_tensor.nii.gz is present 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 whether a registered DTI tensor already exists for *subject_id*.

    Parameters
    ----------
    project_dir : str
        BIDS project root directory.
    subject_id : str
        Subject identifier.

    Returns
    -------
    bool
        *True* if ``DTI_coregT1_tensor.nii.gz`` is present 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
    return (Path(m2m_dir) / const.FILE_DTI_TENSOR).exists()