Skip to content

tissue_analyzer

tit.pre.tissue_analyzer

Tissue Analysis for TI-toolbox.

Analyzes tissue types (CSF, bone, skin) from segmented NIfTI data, calculating volumes, thickness, and generating visualizations.

TissueAnalyzer

TissueAnalyzer(nifti_path: str | Path, output_dir: str | Path, tissue_type: str, logger: Logger)

Analyzes tissue from segmented NIfTI data.

Source code in tit/pre/tissue_analyzer.py
def __init__(
    self,
    nifti_path: str | Path,
    output_dir: str | Path,
    tissue_type: str,
    logger: logging.Logger,
):
    self.nifti_path = Path(nifti_path)
    self.output_dir = Path(output_dir)
    self.output_dir.mkdir(parents=True, exist_ok=True)
    self.logger = logger

    if tissue_type not in TISSUE_CONFIGS:
        raise PreprocessError(f"Unknown tissue type: {tissue_type}")

    config = TISSUE_CONFIGS[tissue_type]
    self.tissue_name = config["name"]
    self.tissue_labels = config["labels"]
    self.padding = config["padding"]
    self.color_scheme = config["color_scheme"]
    self.tissue_color = config["tissue_color"]
    self.brain_labels = config["brain_labels"]

    # Load NIfTI
    self.logger.info(f"Loading NIfTI: {nifti_path}")
    self.nii = nib.load(str(nifti_path))
    self.data = self.nii.get_fdata()
    self.voxel_dims = self.nii.header.get_zooms()[:3]
    self.voxel_volume = float(np.prod(self.voxel_dims))

    # Load label names
    self.label_names = self._load_label_names()

analyze

analyze() -> dict

Run the complete tissue analysis.

Source code in tit/pre/tissue_analyzer.py
def analyze(self) -> dict:
    """Run the complete tissue analysis."""
    self.logger.info(f"Analyzing {self.tissue_name}...")

    # Create masks
    tissue_mask = self._create_tissue_mask()
    brain_mask = self._create_brain_mask()

    total_voxels = int(np.sum(tissue_mask))
    if total_voxels == 0:
        self.logger.warning(f"No {self.tissue_name} tissue found")
        return {
            "volume_cm3": 0,
            "thickness": {"mean": 0, "std": 0, "min": 0, "max": 0},
        }

    # Filter to brain region
    if np.sum(brain_mask) > 0:
        filtered_mask = self._filter_to_brain_region(tissue_mask, brain_mask)
    else:
        filtered_mask = tissue_mask

    filtered_voxels = int(np.sum(filtered_mask))
    self.logger.debug(
        f"Voxels: {total_voxels:,} total, {filtered_voxels:,} filtered"
    )

    # Calculate thickness
    thickness_stats = self._calculate_thickness(filtered_mask)

    # Calculate volume
    volume_mm3 = filtered_voxels * self.voxel_volume
    volume_cm3 = volume_mm3 / 1000

    # Generate outputs
    self._create_visualizations(
        tissue_mask, brain_mask, filtered_mask, thickness_stats
    )
    report_path = self._write_report(tissue_mask, filtered_mask, thickness_stats)

    self.logger.info(
        f"{self.tissue_name}: Volume={volume_cm3:.2f}cm³, "
        f"Thickness={thickness_stats['mean']:.2f}±{thickness_stats['std']:.2f}mm"
    )

    return {
        "volume_cm3": volume_cm3,
        "volume_mm3": volume_mm3,
        "thickness": {
            "mean": thickness_stats["mean"],
            "std": thickness_stats["std"],
            "min": thickness_stats["min"],
            "max": thickness_stats["max"],
        },
        "voxels": {"total": total_voxels, "filtered": filtered_voxels},
        "report_path": str(report_path),
    }

run_tissue_analysis

run_tissue_analysis(project_dir: str, subject_id: str, *, tissues: Iterable[str] = DEFAULT_TISSUES, logger: Logger, runner: CommandRunner | None = None) -> dict

Run tissue analysis for a subject.

Parameters

project_dir : str BIDS project root. subject_id : str Subject identifier without the 'sub-' prefix. tissues : iterable of str Tissue types to analyze (default: bone, csf, skin). logger : logging.Logger Logger for progress output. runner : CommandRunner, optional Not used, kept for API compatibility.

Returns

dict Analysis results for each tissue type.

Source code in tit/pre/tissue_analyzer.py
def run_tissue_analysis(
    project_dir: str,
    subject_id: str,
    *,
    tissues: Iterable[str] = DEFAULT_TISSUES,
    logger: logging.Logger,
    runner: CommandRunner | None = None,
) -> dict:
    """Run tissue analysis for a subject.

    Parameters
    ----------
    project_dir : str
        BIDS project root.
    subject_id : str
        Subject identifier without the 'sub-' prefix.
    tissues : iterable of str
        Tissue types to analyze (default: bone, csf, skin).
    logger : logging.Logger
        Logger for progress output.
    runner : CommandRunner, optional
        Not used, kept for API compatibility.

    Returns
    -------
    dict
        Analysis results for each tissue type.
    """
    pm = get_path_manager(project_dir)

    label_path = Path(pm.tissue_labeling(subject_id))
    if not label_path.exists():
        raise PreprocessError(f"Labeling.nii.gz not found: {label_path}")

    output_root = Path(pm.ensure(pm.tissue_analysis_output(subject_id)))
    results = {}

    for tissue in tissues:
        if tissue not in TISSUE_CONFIGS:
            logger.warning(f"Unknown tissue type: {tissue}, skipping")
            continue

        output_dir = output_root / f"{tissue}_analysis"
        analyzer = TissueAnalyzer(label_path, output_dir, tissue, logger)
        results[tissue] = analyzer.analyze()

    return results