Skip to content

atlas

tit.atlas

Shared atlas module for TI-Toolbox.

Provides mesh (surface) and voxel (volumetric) atlas discovery, region listing, and overlap analysis.

MeshAtlasManager

MeshAtlasManager(seg_dir: str)

Discovers and queries FreeSurfer .annot mesh atlases.

Parameters:

Name Type Description Default
seg_dir str

Path to m2m_{subject}/segmentation/ directory.

required
Source code in tit/atlas/mesh.py
def __init__(self, seg_dir: str) -> None:
    self.seg_dir = seg_dir

list_atlases

list_atlases() -> list[str]

List available mesh atlas names.

Returns:

Type Description
list[str]

Sorted list of atlas names (always includes builtins).

Source code in tit/atlas/mesh.py
def list_atlases(self) -> list[str]:
    """List available mesh atlas names.

    Returns:
        Sorted list of atlas names (always includes builtins).
    """
    discovered: list[str] = []
    if os.path.isdir(self.seg_dir):
        for fname in os.listdir(self.seg_dir):
            if fname.startswith("lh.") and fname.endswith(".annot"):
                stem = fname[3:-6]
                atlas_name = stem.split("_", 1)[-1] if "_" in stem else stem
                if (
                    atlas_name not in discovered
                    and atlas_name not in BUILTIN_ATLASES
                ):
                    discovered.append(atlas_name)
    return sorted(set(BUILTIN_ATLASES + discovered))

list_regions

list_regions(atlas_name: str) -> list[str]

List regions for a mesh atlas from .annot files.

Returns:

Type Description
list[str]

Sorted list of region names with hemisphere suffix (e.g. "precentral-lh").

Source code in tit/atlas/mesh.py
def list_regions(self, atlas_name: str) -> list[str]:
    """List regions for a mesh atlas from .annot files.

    Returns:
        Sorted list of region names with hemisphere suffix (e.g. "precentral-lh").
    """
    if os.path.isdir(self.seg_dir):
        import nibabel.freesurfer as nfs

        regions: list[str] = []
        for hemi in ("lh", "rh"):
            matches = glob.glob(
                os.path.join(self.seg_dir, f"{hemi}.*{atlas_name}.annot")
            )
            if not matches:
                continue
            _, _, names = nfs.read_annot(matches[0])
            for n in names:
                name = n.decode("utf-8") if isinstance(n, bytes) else str(n)
                if name != "unknown":
                    regions.append(f"{name}-{hemi}")
        if regions:
            return sorted(regions)

    return []

find_atlas_file

find_atlas_file(atlas_name: str, hemisphere: str) -> str | None

Find the .annot file path for a given atlas and hemisphere.

Returns:

Type Description
str | None

Path to the .annot file, or None if not found.

Source code in tit/atlas/mesh.py
def find_atlas_file(self, atlas_name: str, hemisphere: str) -> str | None:
    """Find the .annot file path for a given atlas and hemisphere.

    Returns:
        Path to the .annot file, or None if not found.
    """
    if not os.path.isdir(self.seg_dir):
        return None
    matches = glob.glob(
        os.path.join(self.seg_dir, f"{hemisphere}.*{atlas_name}.annot")
    )
    return matches[0] if matches else None

find_all_atlases

find_all_atlases(hemisphere: str) -> dict[str, str]

Find all available atlas files for a hemisphere.

Returns:

Type Description
dict[str, str]

Dict mapping atlas display name to file path.

Source code in tit/atlas/mesh.py
def find_all_atlases(self, hemisphere: str) -> dict[str, str]:
    """Find all available atlas files for a hemisphere.

    Returns:
        Dict mapping atlas display name to file path.
    """
    atlas_map: dict[str, str] = {}
    if not os.path.isdir(self.seg_dir):
        return atlas_map
    pattern = f"{hemisphere}.*.annot"
    for atlas_file in sorted(glob.glob(os.path.join(self.seg_dir, pattern))):
        fname = os.path.basename(atlas_file)
        parts = fname.split(".")
        if len(parts) == 3 and parts[2] == "annot":
            atlas_full = parts[1]
            atlas_display = (
                atlas_full.split("_", 1)[-1] if "_" in atlas_full else atlas_full
            )
            atlas_map[atlas_display] = atlas_file
    return atlas_map

list_annot_regions

list_annot_regions(annot_path: str) -> list[tuple[int, str]]

List all regions in a .annot file.

Returns:

Type Description
list[tuple[int, str]]

List of (region_index, region_name) tuples.

Source code in tit/atlas/mesh.py
def list_annot_regions(self, annot_path: str) -> list[tuple[int, str]]:
    """List all regions in a .annot file.

    Returns:
        List of (region_index, region_name) tuples.
    """
    import nibabel.freesurfer.io as fsio

    labels, ctab, names = fsio.read_annot(annot_path)
    regions = []
    for i, name in enumerate(names):
        region_name = name.decode("utf-8") if isinstance(name, bytes) else str(name)
        regions.append((i, region_name))
    return regions

VoxelAtlasManager

VoxelAtlasManager(freesurfer_mri_dir: str = '', seg_dir: str = '')

Discovers and queries volumetric atlas files.

All discovery methods use the same canonical VOXEL_ATLAS_FILES list so that the analyzer, flex-search, and NIfTI viewer show identical atlases.

Parameters:

Name Type Description Default
freesurfer_mri_dir str

Path to FreeSurfer mri/ directory.

''
seg_dir str

Path to m2m_{subject}/segmentation/ directory.

''
Source code in tit/atlas/voxel.py
def __init__(self, freesurfer_mri_dir: str = "", seg_dir: str = "") -> None:
    self.freesurfer_mri_dir = freesurfer_mri_dir
    self.seg_dir = seg_dir

list_atlases

list_atlases() -> list[tuple[str, str]]

Discover available voxel atlas files for a subject.

Checks FreeSurfer mri/ for VOXEL_ATLAS_FILES and segmentation/ for labeling.nii.gz. Used by analyzer tab, flex subcortical tab, and NIfTI viewer.

Returns:

Type Description
list[tuple[str, str]]

List of (display_name, full_path) tuples.

Source code in tit/atlas/voxel.py
def list_atlases(self) -> list[tuple[str, str]]:
    """Discover available voxel atlas files for a subject.

    Checks FreeSurfer mri/ for VOXEL_ATLAS_FILES and segmentation/
    for labeling.nii.gz.  Used by analyzer tab, flex subcortical tab,
    and NIfTI viewer.

    Returns:
        List of (display_name, full_path) tuples.
    """
    results: list[tuple[str, str]] = []

    if self.freesurfer_mri_dir and os.path.isdir(self.freesurfer_mri_dir):
        for name in VOXEL_ATLAS_FILES:
            path = os.path.join(self.freesurfer_mri_dir, name)
            if os.path.isfile(path):
                results.append((name, path))

    if self.seg_dir:
        labeling = os.path.join(self.seg_dir, "labeling.nii.gz")
        if os.path.isfile(labeling):
            results.append(("labeling.nii.gz", labeling))

    return results

list_regions

list_regions(atlas_path: str) -> list[str]

List regions in a voxel atlas using mri_segstats.

Caches the label file next to the atlas so subsequent calls are fast.

Returns:

Type Description
list[str]

Sorted list of "RegionName (ID: N)" strings.

Source code in tit/atlas/voxel.py
def list_regions(self, atlas_path: str) -> list[str]:
    """List regions in a voxel atlas using mri_segstats.

    Caches the label file next to the atlas so subsequent calls are fast.

    Returns:
        Sorted list of "RegionName (ID: N)" strings.
    """
    atlas_bname = os.path.splitext(os.path.basename(atlas_path))[0]
    if atlas_bname.endswith(".nii"):
        atlas_bname = os.path.splitext(atlas_bname)[0]
    labels_file = os.path.join(
        os.path.dirname(atlas_path), f"{atlas_bname}_labels.txt"
    )

    if not os.path.isfile(labels_file):
        cmd = [
            "mri_segstats",
            "--seg",
            atlas_path,
            "--excludeid",
            "0",
            "--ctab-default",
            "--sum",
            labels_file,
        ]
        subprocess.run(cmd, check=True, capture_output=True)

    regions: list[str] = []
    in_header = True
    with open(labels_file) as fh:
        for line in fh:
            if in_header and not line.startswith("#"):
                in_header = False
            if not in_header and line.strip():
                parts = line.strip().split()
                if len(parts) >= 5:
                    name = " ".join(parts[4:])
                    seg_id = parts[1]
                    regions.append(f"{name} (ID: {seg_id})")

    return sorted(set(regions))

detect_mni_atlases staticmethod

detect_mni_atlases(atlas_dir: str) -> list[str]

Detect available MNI atlases in an assets directory.

Parameters:

Name Type Description Default
atlas_dir str

Path to the atlas resources directory.

required

Returns:

Type Description
list[str]

List of full paths to found MNI atlas files.

Source code in tit/atlas/voxel.py
@staticmethod
def detect_mni_atlases(atlas_dir: str) -> list[str]:
    """Detect available MNI atlases in an assets directory.

    Args:
        atlas_dir: Path to the atlas resources directory.

    Returns:
        List of full paths to found MNI atlas files.
    """
    if not os.path.isdir(atlas_dir):
        return []
    return [
        os.path.join(atlas_dir, p)
        for p in MNI_ATLAS_FILES
        if os.path.isfile(os.path.join(atlas_dir, p))
    ]

find_labeling_lut

find_labeling_lut() -> str | None

Find the LUT file for the SimNIBS labeling atlas.

Returns:

Type Description
str | None

Path to labeling_LUT.txt if it exists, else None.

Source code in tit/atlas/voxel.py
def find_labeling_lut(self) -> str | None:
    """Find the LUT file for the SimNIBS labeling atlas.

    Returns:
        Path to labeling_LUT.txt if it exists, else None.
    """
    lut_path = os.path.join(self.seg_dir, "labeling_LUT.txt")
    return lut_path if os.path.isfile(lut_path) else None

atlas_overlap_analysis

atlas_overlap_analysis(sig_mask, atlas_files: list[str], data_dir: str, reference_img=None) -> dict[str, list]

Analyze overlap between significant voxels and atlas regions.

Parameters:

Name Type Description Default
sig_mask

Binary ndarray (x, y, z) of significant voxels.

required
atlas_files list[str]

List of atlas file names.

required
data_dir str

Directory containing atlas files.

required
reference_img

nibabel image for resampling (optional).

None

Returns:

Type Description
dict[str, list]

Dict mapping atlas names to lists of region overlap dicts.

Source code in tit/atlas/overlap.py
def atlas_overlap_analysis(
    sig_mask,
    atlas_files: list[str],
    data_dir: str,
    reference_img=None,
) -> dict[str, list]:
    """Analyze overlap between significant voxels and atlas regions.

    Args:
        sig_mask: Binary ndarray (x, y, z) of significant voxels.
        atlas_files: List of atlas file names.
        data_dir: Directory containing atlas files.
        reference_img: nibabel image for resampling (optional).

    Returns:
        Dict mapping atlas names to lists of region overlap dicts.
    """
    import numpy as np
    import nibabel as nib

    logger.info("\n" + "=" * 60)
    logger.info("ATLAS OVERLAP ANALYSIS")
    logger.info("=" * 60)

    results: dict[str, list] = {}

    for atlas_file in atlas_files:
        atlas_path = os.path.join(data_dir, atlas_file)
        if not os.path.exists(atlas_path):
            logger.warning("Atlas file not found - %s", atlas_file)
            continue

        logger.info("\n--- %s ---", atlas_file)
        atlas_img = nib.load(atlas_path)

        if reference_img is not None:
            atlas_data = check_and_resample_atlas(atlas_img, reference_img, atlas_file)
        else:
            atlas_data = atlas_img.get_fdata().astype(int)

        regions = np.unique(atlas_data[atlas_data > 0])

        region_counts = []
        for region_id in regions:
            region_mask = atlas_data == region_id
            overlap = np.sum(sig_mask & region_mask)

            if overlap > 0:
                region_counts.append(
                    {
                        "region_id": int(region_id),
                        "overlap_voxels": int(overlap),
                        "region_size": int(np.sum(region_mask)),
                    }
                )

        region_counts = sorted(
            region_counts, key=lambda x: x["overlap_voxels"], reverse=True
        )

        logger.info("\nTop regions by significant voxel count:")
        for i, r in enumerate(region_counts[:15], 1):
            pct = 100 * r["overlap_voxels"] / r["region_size"]
            logger.info(
                "%2d. Region %3d: %4d sig. voxels (%.1f%% of region)",
                i,
                r["region_id"],
                r["overlap_voxels"],
                pct,
            )

        results[atlas_file] = region_counts

    return results

check_and_resample_atlas

check_and_resample_atlas(atlas_img, reference_img, atlas_name: str)

Check if atlas dimensions match reference, resample if needed.

Parameters:

Name Type Description Default
atlas_img

nibabel image of the atlas.

required
reference_img

nibabel image of the reference (subject data).

required
atlas_name str

Name of atlas for logging.

required

Returns:

Type Description

Atlas data as integer ndarray in correct dimensions.

Source code in tit/atlas/overlap.py
def check_and_resample_atlas(atlas_img, reference_img, atlas_name: str):
    """Check if atlas dimensions match reference, resample if needed.

    Args:
        atlas_img: nibabel image of the atlas.
        reference_img: nibabel image of the reference (subject data).
        atlas_name: Name of atlas for logging.

    Returns:
        Atlas data as integer ndarray in correct dimensions.
    """
    import numpy as np
    from nibabel.processing import resample_from_to
    import nibabel as nib

    atlas_shape = atlas_img.shape
    ref_shape = reference_img.shape

    logger.info("  Atlas shape: %s", atlas_shape)
    logger.info("  Reference shape: %s", ref_shape[:3])

    if atlas_shape[:3] != ref_shape[:3]:
        logger.info("  Dimensions don't match. Resampling atlas...")

        if len(ref_shape) > 3:
            ref_data_3d = reference_img.get_fdata()[:, :, :, 0]
        else:
            ref_data_3d = reference_img.get_fdata()

        ref_img_3d = nib.Nifti1Image(
            ref_data_3d.astype(np.float32),
            reference_img.affine[:4, :4],
            None,
        )

        atlas_data_raw = atlas_img.get_fdata()
        if len(atlas_data_raw.shape) > 3:
            atlas_data_raw = atlas_data_raw[:, :, :, 0]

        atlas_img_3d = nib.Nifti1Image(
            atlas_data_raw.astype(np.float32),
            atlas_img.affine[:4, :4],
            None,
        )

        resampled_atlas = resample_from_to(atlas_img_3d, ref_img_3d, order=0)
        atlas_data = resampled_atlas.get_fdata().astype(int)
        logger.info("  Resampled to: %s", atlas_data.shape)
    else:
        logger.info("  Dimensions match.")
        atlas_data = atlas_img.get_fdata().astype(int)
        if len(atlas_data.shape) > 3:
            atlas_data = atlas_data[:, :, :, 0]

    return atlas_data