Skip to content

Core Utilities

TI-Toolbox provides opinionated, BIDS-compliant infrastructure for path resolution, logging, constants, and config serialization. These modules enforce a strict project structure so that all pipeline stages produce consistent, discoverable outputs.

graph LR
    IMPORT(["import tit"]) --> LOG[Logger]
    IMPORT --> PM[PathManager]
    IMPORT --> CONFIG[Config IO]
    PM --> PATHS([BIDS Paths])
    LOG --> FILE([File Logs])
    CONFIG --> JSON([JSON Files])
    JSON --> CLI[CLI Subprocesses]
    style IMPORT fill:#1a3a5c,stroke:#48a,color:#fff
    style LOG fill:#2d5a27,stroke:#4a8,color:#fff
    style PM fill:#2d5a27,stroke:#4a8,color:#fff
    style CONFIG fill:#2d5a27,stroke:#4a8,color:#fff
    style CLI fill:#2d5a27,stroke:#4a8,color:#fff
    style PATHS fill:#1a5c4a,stroke:#4a8,color:#fff
    style FILE fill:#1a5c4a,stroke:#4a8,color:#fff
    style JSON fill:#1a5c4a,stroke:#4a8,color:#fff

Initialization

Just import — logging and path resolution are automatic:

from tit.sim import SimulationConfig, run_simulation

Importing any tit module configures the tit logger hierarchy and attaches a stdout handler at INFO level. PathManager auto-detects the project directory from the PROJECT_DIR or PROJECT_DIR_NAME environment variables inside Docker containers. No explicit initialization calls are needed.

PathManager

PathManager is a singleton that enforces the BIDS directory layout. All modules use it instead of constructing paths manually.

Project-Level Paths

These methods take no arguments and return top-level directories or files:

Method Returns
pm.derivatives() <project>/derivatives
pm.sourcedata() <project>/sourcedata
pm.simnibs() <project>/derivatives/SimNIBS
pm.freesurfer() <project>/derivatives/freesurfer
pm.ti_toolbox() <project>/derivatives/ti-toolbox
pm.config_dir() <project>/code/ti-toolbox/config
pm.montage_config() <project>/code/ti-toolbox/config/montage_list.json
pm.project_status() <project>/code/ti-toolbox/config/project_status.json
pm.reports() <project>/derivatives/ti-toolbox/reports
pm.stats_data() <project>/derivatives/ti-toolbox/stats/data
pm.qsiprep() <project>/derivatives/qsiprep
pm.qsirecon() <project>/derivatives/qsirecon

Subject-Level Paths

Methods that accept a subject ID (sid) string without the sub- prefix:

Method Returns
pm.sub("001") <simnibs>/sub-001
pm.m2m("001") <simnibs>/sub-001/m2m_001
pm.t1("001") .../m2m_001/T1.nii.gz
pm.segmentation("001") .../m2m_001/segmentation
pm.tissue_labeling("001") .../segmentation/labeling.nii.gz
pm.eeg_positions("001") .../m2m_001/eeg_positions
pm.rois("001") .../m2m_001/ROIs
pm.simulations("001") <simnibs>/sub-001/Simulations
pm.leadfields("001") <simnibs>/sub-001/leadfields
pm.ex_search("001") <simnibs>/sub-001/ex-search
pm.flex_search("001") <simnibs>/sub-001/flex-search
pm.logs("001") <ti-toolbox>/logs/sub-001
pm.tissue_analysis_output("001") <ti-toolbox>/tissue_analysis/sub-001
pm.bids_subject("001") <project>/sub-001
pm.bids_anat("001") <project>/sub-001/anat
pm.bids_dwi("001") <project>/sub-001/dwi
pm.freesurfer_subject("001") <freesurfer>/sub-001
pm.freesurfer_mri("001") <freesurfer>/sub-001/mri
pm.sourcedata_subject("001") <sourcedata>/sub-001
pm.qsiprep_subject("001") <qsiprep>/sub-001
pm.qsirecon_subject("001") <qsirecon>/sub-001

Simulation-Level Paths

Methods that accept a subject ID and simulation name:

Method Returns
pm.simulation("001", "motor") .../Simulations/motor
pm.ti_mesh("001", "motor") .../TI/mesh/motor_TI.msh
pm.ti_mesh_dir("001", "motor") .../TI/mesh
pm.ti_central_surface("001", "motor") .../TI/mesh/surfaces/motor_TI_central.msh
pm.mti_mesh_dir("001", "motor") .../mTI/mesh
pm.analysis_dir("001", "motor", "mesh") .../Analyses/Mesh
pm.analysis_dir("001", "motor", "voxel") .../Analyses/Voxel

Additional paths for optimization runs and statistics:

Method Returns
pm.flex_search_run("001", "run_01") .../flex-search/run_01
pm.flex_manifest("001", "run_01") .../flex-search/run_01/flex_meta.json
pm.flex_electrode_positions("001", "run_01") .../flex-search/run_01/electrode_positions.json
pm.ex_search_run("001", "run_01") .../ex-search/run_01
pm.sourcedata_dicom("001", "anat") <sourcedata>/sub-001/anat/dicom
pm.stats_output("group_comparison", "motor_study") <ti-toolbox>/stats/group_comparison/motor_study
pm.logs_group() <ti-toolbox>/logs/group_analysis

Listing Methods

pm.list_simnibs_subjects()       # ["001", "002"] — subjects with m2m folders
pm.list_simulations("001")       # ["motor_cortex", "frontal"]
pm.list_eeg_caps("001")          # ["GSN-HydroCel-185.csv"]
pm.list_flex_search_runs("001")  # ["run_01"] — runs with metadata files

Utility

pm.ensure("/some/path")  # creates directory (with parents) and returns the path

Logging

TI-Toolbox logging is file-first. The tit logger hierarchy has propagate=False, so nothing reaches the terminal unless you explicitly opt in.

Function Purpose
setup_logging(level) Configure the tit logger level; adds NO handlers
add_file_handler(log_file, level, logger_name) Attach a FileHandler (append mode); creates parent dirs
add_stream_handler(logger_name, level) Attach a StreamHandler (stdout)
get_file_only_logger(name, log_file, level) Return a logger that writes ONLY to the given file

Typical Patterns

Terminal output (automatic on import — nothing to do):

import tit  # auto-initializes: setup_logging("INFO") + add_stream_handler("tit", "INFO")

File logging (opt-in, used by pipeline modules):

from tit import add_file_handler

fh = add_file_handler("/data/logs/run.log", level="DEBUG")

Isolated file logger (used per-analysis):

from tit.logger import get_file_only_logger

log = get_file_only_logger("roi_analysis", "/data/logs/roi.log")
log.info("Analyzing ROI...")

Log Format

File handlers:

2025-01-15 14:30:00 | INFO | tit.sim.simulator | Simulation started

Stream handlers use minimal format: %(message)s.

Constants

All hardcoded values live in tit.constants. Key categories:

Category Examples
Directory names DIR_DERIVATIVES, DIR_SIMNIBS, DIR_FLEX_SEARCH, DIR_ANALYSIS
File names FILE_MONTAGE_LIST, FILE_T1, FILE_EGI_TEMPLATE
File extensions EXT_NIFTI (.nii.gz), EXT_MESH (.msh), EXT_CSV
BIDS prefixes PREFIX_SUBJECT (sub-), PREFIX_SESSION (ses-)
Field names FIELD_TI_MAX (TI_max), FIELD_MTI_MAX (TI_Max), FIELD_TI_NORMAL (TI_normal)
Tissue tags GM_TISSUE_TAG (2), WM_TISSUE_TAG (1), BRAIN_TISSUE_TAG_RANGES
Conductivities CONDUCTIVITY_GRAY_MATTER (0.275 S/m), CONDUCTIVITY_WHITE_MATTER (0.126 S/m), 12 tissues total
Tissue properties TISSUE_PROPERTIES — list of dicts with number, name, conductivity, and reference
Atlas names ATLAS_DK40, ATLAS_A2009S, ATLAS_ASEG, ATLAS_APARC_ASEG
Analysis defaults DEFAULT_PERCENTILES ([95, 99, 99.9]), DEFAULT_FOCALITY_CUTOFFS ([50, 75, 90, 95]), DEFAULT_RADIUS_MM (5.0)
Simulation SIM_TYPE_TI, SIM_TYPE_MTI, ELECTRODE_SHAPE_ELLIPSE, DEFAULT_INTENSITY (1.0)
EEG nets EEG_NETS — list of dicts with value, label, electrode_count
Validation bounds VALIDATION_BOUNDS — min/max for radius, current, iterations, etc.
Plot settings PLOT_DPI (600), PLOT_FIGSIZE_DEFAULT ((10, 8))
Timestamps TIMESTAMP_FORMAT_DEFAULT (%Y%m%d_%H%M%S), TIMESTAMP_FORMAT_READABLE
QSI integration QSI_RECON_SPECS, QSI_ATLASES, QSI_DEFAULT_CPUS (8)
from tit import constants as const

const.FIELD_TI_MAX         # "TI_max"
const.GM_TISSUE_TAG        # 2
const.DEFAULT_RADIUS_MM    # 5.0
const.TISSUE_PROPERTIES    # [{"number": 1, "name": "White Matter", ...}, ...]

Config IO

The tit.config_io module serializes typed config dataclasses to JSON for CLI subprocesses. This is the mechanism the GUI uses to pass configurations to optimizer and analyzer processes.

from tit.config_io import write_config_json, read_config_json

# Write: dataclass -> temp JSON file, returns path
path = write_config_json(my_flex_config, prefix="flex")

# Read: JSON file -> plain dict
data = read_config_json(path)

Union-typed fields (ROI specs, electrode specs) get a _type discriminator so the subprocess can reconstruct the correct type:

Class _type value
FlexConfig.SphericalROI "SphericalROI"
FlexConfig.AtlasROI "AtlasROI"
FlexConfig.SubcorticalROI "SubcorticalROI"
ExConfig.PoolElectrodes "PoolElectrodes"
ExConfig.BucketElectrodes "BucketElectrodes"
Montage "Montage"

Error Handling

Custom exceptions are defined in domain-specific modules:

Exception Module Base Class When Raised
PreprocessError tit.pre.utils RuntimeError A preprocessing step fails
PreprocessCancelled tit.pre.utils RuntimeError User cancels a preprocessing run
DockerBuildError tit.pre.qsi.docker_builder Exception Docker command construction fails
from tit.pre.utils import PreprocessError, PreprocessCancelled

try:
    run_pipeline(config)
except PreprocessCancelled:
    print("Pipeline was cancelled")
except PreprocessError as e:
    print(f"Pipeline failed: {e}")

API Reference

Path Management

tit.paths.PathManager

PathManager(project_dir: str | None = None)

BIDS-compliant path resolution for TI-Toolbox projects.

Provides methods to resolve file and directory paths within a BIDS-structured project, including subject anatomical data, SimNIBS derivatives, FreeSurfer outputs, optimization runs, and analysis results.

The project directory can be set explicitly or auto-detected from the PROJECT_DIR / PROJECT_DIR_NAME environment variables (useful inside Docker containers).

Parameters

project_dir : str or None, optional Root directory of the BIDS project. If None, the directory is auto-detected from environment variables on first access.

Attributes

project_dir : str or None Resolved project root, or None if not yet set / detected. project_dir_name : str or None Basename of :attr:project_dir.

See Also

get_path_manager : Obtain the global singleton instance. reset_path_manager : Destroy the singleton for testing or re-init.

Source code in tit/paths.py
def __init__(self, project_dir: str | None = None):
    self._project_dir: str | None = None
    if project_dir:
        self.project_dir = project_dir

project_dir property writable

project_dir: str | None

Project root directory, auto-detected from environment if unset.

project_dir_name property

project_dir_name: str | None

Basename of :attr:project_dir, or the PROJECT_DIR_NAME env var.

derivatives

derivatives() -> str

Path to <project>/derivatives/.

Source code in tit/paths.py
def derivatives(self) -> str:
    """Path to ``<project>/derivatives/``."""
    return os.path.join(self._root(), "derivatives")

sourcedata

sourcedata() -> str

Path to <project>/sourcedata/.

Source code in tit/paths.py
def sourcedata(self) -> str:
    """Path to ``<project>/sourcedata/``."""
    return os.path.join(self._root(), "sourcedata")

simnibs

simnibs() -> str

Path to <project>/derivatives/SimNIBS/.

Source code in tit/paths.py
def simnibs(self) -> str:
    """Path to ``<project>/derivatives/SimNIBS/``."""
    return os.path.join(self._root(), "derivatives", "SimNIBS")

freesurfer

freesurfer() -> str

Path to <project>/derivatives/freesurfer/.

Source code in tit/paths.py
def freesurfer(self) -> str:
    """Path to ``<project>/derivatives/freesurfer/``."""
    return os.path.join(self._root(), "derivatives", "freesurfer")

ti_toolbox

ti_toolbox() -> str

Path to <project>/derivatives/ti-toolbox/.

Source code in tit/paths.py
def ti_toolbox(self) -> str:
    """Path to ``<project>/derivatives/ti-toolbox/``."""
    return os.path.join(self._root(), "derivatives", "ti-toolbox")

config_dir

config_dir() -> str

Path to <project>/code/ti-toolbox/config/.

Source code in tit/paths.py
def config_dir(self) -> str:
    """Path to ``<project>/code/ti-toolbox/config/``."""
    return os.path.join(self._root(), "code", "ti-toolbox", "config")

montage_config

montage_config() -> str

Path to the montage_list.json configuration file.

Source code in tit/paths.py
def montage_config(self) -> str:
    """Path to the ``montage_list.json`` configuration file."""
    return os.path.join(self.config_dir(), "montage_list.json")

project_status

project_status() -> str

Path to the project_status.json file.

Source code in tit/paths.py
def project_status(self) -> str:
    """Path to the ``project_status.json`` file."""
    return os.path.join(self.config_dir(), "project_status.json")

extensions_config

extensions_config() -> str

Path to the extensions.json configuration file.

Uses the user-level config directory so that extension preferences persist across projects and container restarts.

Source code in tit/paths.py
def extensions_config(self) -> str:
    """Path to the ``extensions.json`` configuration file.

    Uses the user-level config directory so that extension
    preferences persist across projects and container restarts.
    """
    return os.path.join(self.user_config_dir(), "extensions.json")

user_config_dir staticmethod

user_config_dir() -> str

Path to the user-level config directory.

Returns the directory that persists across projects and container restarts. Inside Docker this is /root/.config/ti-toolbox (mounted from the host by the Electron launcher). Outside Docker the platform-native config directory is used:

  • macOS: ~/.config/ti-toolbox
  • Linux: $XDG_CONFIG_HOME/ti-toolbox (default ~/.config)
  • Windows: %APPDATA%/ti-toolbox

The directory is created if it does not exist.

Returns

str Absolute path to the user config directory.

Source code in tit/paths.py
@staticmethod
def user_config_dir() -> str:
    """Path to the user-level config directory.

    Returns the directory that persists across projects and container
    restarts.  Inside Docker this is ``/root/.config/ti-toolbox``
    (mounted from the host by the Electron launcher).  Outside Docker
    the platform-native config directory is used:

    - **macOS**: ``~/.config/ti-toolbox``
    - **Linux**: ``$XDG_CONFIG_HOME/ti-toolbox`` (default ``~/.config``)
    - **Windows**: ``%APPDATA%/ti-toolbox``

    The directory is created if it does not exist.

    Returns
    -------
    str
        Absolute path to the user config directory.
    """
    import platform as _platform
    import sys as _sys

    # Inside Docker the Electron launcher mounts the host config here.
    docker_path = os.path.join("/root", ".config", "ti-toolbox")
    if os.path.isdir(docker_path):
        return docker_path

    # Outside Docker: platform-native paths
    system = _platform.system()
    if system == "Darwin":
        # Use ~/.config (NOT ~/Library/Application Support which is
        # Electron's userData dir).  Matches env.js getUserConfigDir().
        base = os.path.join(os.path.expanduser("~"), ".config")
    elif system == "Windows":
        base = os.environ.get(
            "APPDATA", os.path.join(os.path.expanduser("~"), "AppData", "Roaming")
        )
    else:  # Linux / other
        base = os.environ.get(
            "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")
        )

    config_dir = os.path.join(base, "ti-toolbox")
    os.makedirs(config_dir, exist_ok=True)
    return config_dir

reports

reports() -> str

Path to <project>/derivatives/ti-toolbox/reports/.

Source code in tit/paths.py
def reports(self) -> str:
    """Path to ``<project>/derivatives/ti-toolbox/reports/``."""
    return os.path.join(self.ti_toolbox(), "reports")

stats_data

stats_data() -> str

Path to <project>/derivatives/ti-toolbox/stats/data/.

Source code in tit/paths.py
def stats_data(self) -> str:
    """Path to ``<project>/derivatives/ti-toolbox/stats/data/``."""
    return os.path.join(self.ti_toolbox(), "stats", "data")

stats_output

stats_output(analysis_type: str, analysis_name: str) -> str

Path to a specific statistics output directory.

Parameters

analysis_type : str Type of statistical analysis (e.g., "permutation"). analysis_name : str Name of the analysis run.

Returns

str Absolute path to the output directory.

Source code in tit/paths.py
def stats_output(self, analysis_type: str, analysis_name: str) -> str:
    """Path to a specific statistics output directory.

    Parameters
    ----------
    analysis_type : str
        Type of statistical analysis (e.g., ``"permutation"``).
    analysis_name : str
        Name of the analysis run.

    Returns
    -------
    str
        Absolute path to the output directory.
    """
    return os.path.join(self.ti_toolbox(), "stats", analysis_type, analysis_name)

logs_group

logs_group() -> str

Path to group-analysis log directory.

Source code in tit/paths.py
def logs_group(self) -> str:
    """Path to group-analysis log directory."""
    return os.path.join(self.ti_toolbox(), "logs", "group_analysis")

qsiprep

qsiprep() -> str

Path to <project>/derivatives/qsiprep/.

Source code in tit/paths.py
def qsiprep(self) -> str:
    """Path to ``<project>/derivatives/qsiprep/``."""
    return os.path.join(self._root(), "derivatives", "qsiprep")

qsirecon

qsirecon() -> str

Path to <project>/derivatives/qsirecon/.

Source code in tit/paths.py
def qsirecon(self) -> str:
    """Path to ``<project>/derivatives/qsirecon/``."""
    return os.path.join(self._root(), "derivatives", "qsirecon")

sub

sub(sid: str) -> str

Path to derivatives/SimNIBS/sub-{sid}/.

Parameters

sid : str Subject identifier (without sub- prefix).

Returns

str Absolute path to the subject's SimNIBS directory.

Source code in tit/paths.py
def sub(self, sid: str) -> str:
    """Path to ``derivatives/SimNIBS/sub-{sid}/``.

    Parameters
    ----------
    sid : str
        Subject identifier (without ``sub-`` prefix).

    Returns
    -------
    str
        Absolute path to the subject's SimNIBS directory.
    """
    return os.path.join(self._root(), "derivatives", "SimNIBS", f"sub-{sid}")

m2m

m2m(sid: str) -> str

Path to the m2m_{sid} head-model directory for sid.

Source code in tit/paths.py
def m2m(self, sid: str) -> str:
    """Path to the ``m2m_{sid}`` head-model directory for *sid*."""
    return os.path.join(self.sub(sid), f"m2m_{sid}")

eeg_positions

eeg_positions(sid: str) -> str

Path to the EEG electrode-position directory for sid.

Source code in tit/paths.py
def eeg_positions(self, sid: str) -> str:
    """Path to the EEG electrode-position directory for *sid*."""
    return os.path.join(self.m2m(sid), "eeg_positions")

rois

rois(sid: str) -> str

Path to the ROI directory for sid.

Source code in tit/paths.py
def rois(self, sid: str) -> str:
    """Path to the ROI directory for *sid*."""
    return os.path.join(self.m2m(sid), "ROIs")

t1

t1(sid: str) -> str

Path to the T1-weighted NIfTI image for sid.

Source code in tit/paths.py
def t1(self, sid: str) -> str:
    """Path to the T1-weighted NIfTI image for *sid*."""
    return os.path.join(self.m2m(sid), "T1.nii.gz")

segmentation

segmentation(sid: str) -> str

Path to the segmentation directory for sid.

Source code in tit/paths.py
def segmentation(self, sid: str) -> str:
    """Path to the segmentation directory for *sid*."""
    return os.path.join(self.m2m(sid), "segmentation")

tissue_labeling

tissue_labeling(sid: str) -> str

Path to the tissue labeling NIfTI for sid.

Source code in tit/paths.py
def tissue_labeling(self, sid: str) -> str:
    """Path to the tissue labeling NIfTI for *sid*."""
    return os.path.join(self.segmentation(sid), "labeling.nii.gz")

leadfields

leadfields(sid: str) -> str

Path to the leadfields directory for sid.

Source code in tit/paths.py
def leadfields(self, sid: str) -> str:
    """Path to the leadfields directory for *sid*."""
    return os.path.join(self.sub(sid), "leadfields")

simulations

simulations(sid: str) -> str

Path to Simulations/ for sid.

Source code in tit/paths.py
def simulations(self, sid: str) -> str:
    """Path to ``Simulations/`` for *sid*."""
    return os.path.join(self.sub(sid), "Simulations")

logs

logs(sid: str) -> str

Path to per-subject log directory for sid.

Source code in tit/paths.py
def logs(self, sid: str) -> str:
    """Path to per-subject log directory for *sid*."""
    return os.path.join(self.ti_toolbox(), "logs", f"sub-{sid}")

tissue_analysis_output

tissue_analysis_output(sid: str) -> str

Path to tissue-analysis output directory for sid.

Source code in tit/paths.py
def tissue_analysis_output(self, sid: str) -> str:
    """Path to tissue-analysis output directory for *sid*."""
    return os.path.join(self.ti_toolbox(), "tissue_analysis", f"sub-{sid}")

bids_subject

bids_subject(sid: str) -> str

Path to <project>/sub-{sid}/ (raw BIDS subject root).

Source code in tit/paths.py
def bids_subject(self, sid: str) -> str:
    """Path to ``<project>/sub-{sid}/`` (raw BIDS subject root)."""
    return os.path.join(self._root(), f"sub-{sid}")

bids_anat

bids_anat(sid: str) -> str

Path to <project>/sub-{sid}/anat/.

Source code in tit/paths.py
def bids_anat(self, sid: str) -> str:
    """Path to ``<project>/sub-{sid}/anat/``."""
    return os.path.join(self.bids_subject(sid), "anat")

bids_dwi

bids_dwi(sid: str) -> str

Path to <project>/sub-{sid}/dwi/.

Source code in tit/paths.py
def bids_dwi(self, sid: str) -> str:
    """Path to ``<project>/sub-{sid}/dwi/``."""
    return os.path.join(self.bids_subject(sid), "dwi")

sourcedata_subject

sourcedata_subject(sid: str) -> str

Path to sourcedata/sub-{sid}/.

Source code in tit/paths.py
def sourcedata_subject(self, sid: str) -> str:
    """Path to ``sourcedata/sub-{sid}/``."""
    return os.path.join(self.sourcedata(), f"sub-{sid}")

freesurfer_subject

freesurfer_subject(sid: str) -> str

Path to derivatives/freesurfer/sub-{sid}/.

Source code in tit/paths.py
def freesurfer_subject(self, sid: str) -> str:
    """Path to ``derivatives/freesurfer/sub-{sid}/``."""
    return os.path.join(self.freesurfer(), f"sub-{sid}")

freesurfer_mri

freesurfer_mri(sid: str) -> str

Path to derivatives/freesurfer/sub-{sid}/mri/.

Source code in tit/paths.py
def freesurfer_mri(self, sid: str) -> str:
    """Path to ``derivatives/freesurfer/sub-{sid}/mri/``."""
    return os.path.join(self.freesurfer_subject(sid), "mri")

qsiprep_subject

qsiprep_subject(sid: str) -> str

Path to derivatives/qsiprep/sub-{sid}/.

Source code in tit/paths.py
def qsiprep_subject(self, sid: str) -> str:
    """Path to ``derivatives/qsiprep/sub-{sid}/``."""
    return os.path.join(self.qsiprep(), f"sub-{sid}")

qsirecon_subject

qsirecon_subject(sid: str) -> str

Path to derivatives/qsirecon/sub-{sid}/.

Source code in tit/paths.py
def qsirecon_subject(self, sid: str) -> str:
    """Path to ``derivatives/qsirecon/sub-{sid}/``."""
    return os.path.join(self.qsirecon(), f"sub-{sid}")
ex_search(sid: str) -> str

Path to exhaustive-search results for sid.

Source code in tit/paths.py
def ex_search(self, sid: str) -> str:
    """Path to exhaustive-search results for *sid*."""
    return os.path.join(self.sub(sid), "ex-search")
flex_search(sid: str) -> str

Path to flex-search results for sid.

Source code in tit/paths.py
def flex_search(self, sid: str) -> str:
    """Path to flex-search results for *sid*."""
    return os.path.join(self.sub(sid), "flex-search")

simulation

simulation(sid: str, sim: str) -> str

Path to a named simulation directory for sid.

Source code in tit/paths.py
def simulation(self, sid: str, sim: str) -> str:
    """Path to a named simulation directory for *sid*."""
    return os.path.join(self.simulations(sid), sim)

ti_mesh

ti_mesh(sid: str, sim: str) -> str

Path to the TI mesh file ({sim}_TI.msh).

Source code in tit/paths.py
def ti_mesh(self, sid: str, sim: str) -> str:
    """Path to the TI mesh file (``{sim}_TI.msh``)."""
    return os.path.join(self.simulation(sid, sim), "TI", "mesh", f"{sim}_TI.msh")

ti_mesh_dir

ti_mesh_dir(sid: str, sim: str) -> str

Path to the TI mesh directory.

Source code in tit/paths.py
def ti_mesh_dir(self, sid: str, sim: str) -> str:
    """Path to the TI mesh directory."""
    return os.path.join(self.simulation(sid, sim), "TI", "mesh")

ti_central_surface

ti_central_surface(sid: str, sim: str) -> str

Path to the TI central cortical surface mesh.

Source code in tit/paths.py
def ti_central_surface(self, sid: str, sim: str) -> str:
    """Path to the TI central cortical surface mesh."""
    return os.path.join(
        self.simulation(sid, sim), "TI", "mesh", "surfaces", f"{sim}_TI_central.msh"
    )

mti_mesh_dir

mti_mesh_dir(sid: str, sim: str) -> str

Path to the mTI mesh directory.

Source code in tit/paths.py
def mti_mesh_dir(self, sid: str, sim: str) -> str:
    """Path to the mTI mesh directory."""
    return os.path.join(self.simulation(sid, sim), "mTI", "mesh")

analysis_dir

analysis_dir(sid: str, sim: str, space: str) -> str

Path to the analysis directory for a given analysis space.

Parameters

sid : str Subject identifier. sim : str Simulation name. space : str Analysis space — "mesh" or "voxel".

Returns

str Absolute path to Analyses/Mesh/ or Analyses/Voxel/.

Source code in tit/paths.py
def analysis_dir(self, sid: str, sim: str, space: str) -> str:
    """Path to the analysis directory for a given analysis space.

    Parameters
    ----------
    sid : str
        Subject identifier.
    sim : str
        Simulation name.
    space : str
        Analysis space — ``"mesh"`` or ``"voxel"``.

    Returns
    -------
    str
        Absolute path to ``Analyses/Mesh/`` or ``Analyses/Voxel/``.
    """
    folder = "Mesh" if space.lower() == "mesh" else "Voxel"
    return os.path.join(self.simulation(sid, sim), "Analyses", folder)

sourcedata_dicom

sourcedata_dicom(sid: str, modality: str) -> str

Path to DICOM source data for sid and modality.

Source code in tit/paths.py
def sourcedata_dicom(self, sid: str, modality: str) -> str:
    """Path to DICOM source data for *sid* and *modality*."""
    return os.path.join(self.sourcedata_subject(sid), modality, "dicom")

ex_search_run

ex_search_run(sid: str, run: str) -> str

Path to a specific exhaustive-search run directory.

Source code in tit/paths.py
def ex_search_run(self, sid: str, run: str) -> str:
    """Path to a specific exhaustive-search run directory."""
    return os.path.join(self.ex_search(sid), run)

flex_search_run

flex_search_run(sid: str, name: str) -> str

Path to a specific flex-search run directory.

Source code in tit/paths.py
def flex_search_run(self, sid: str, name: str) -> str:
    """Path to a specific flex-search run directory."""
    return os.path.join(self.flex_search(sid), name)

flex_electrode_positions

flex_electrode_positions(sid: str, name: str) -> str

Path to electrode_positions.json for a flex-search run.

Source code in tit/paths.py
def flex_electrode_positions(self, sid: str, name: str) -> str:
    """Path to ``electrode_positions.json`` for a flex-search run."""
    return os.path.join(self.flex_search_run(sid, name), "electrode_positions.json")

flex_manifest

flex_manifest(sid: str, name: str) -> str

Path to flex_meta.json for a flex-search run.

Source code in tit/paths.py
def flex_manifest(self, sid: str, name: str) -> str:
    """Path to ``flex_meta.json`` for a flex-search run."""
    return os.path.join(self.flex_search_run(sid, name), "flex_meta.json")

ensure

ensure(path: str) -> str

Create a directory (with parents) if it does not exist.

Parameters

path : str Directory path to create.

Returns

str The same path, for convenient chaining.

Source code in tit/paths.py
def ensure(self, path: str) -> str:
    """Create a directory (with parents) if it does not exist.

    Parameters
    ----------
    path : str
        Directory path to create.

    Returns
    -------
    str
        The same *path*, for convenient chaining.
    """
    os.makedirs(path, exist_ok=True)
    return path

list_simnibs_subjects

list_simnibs_subjects() -> list[str]

List subject IDs that have a SimNIBS head-model (m2m) folder.

Returns

list of str Naturally sorted subject identifiers (without the sub- prefix). Returns an empty list if the SimNIBS directory does not exist.

Source code in tit/paths.py
def list_simnibs_subjects(self) -> list[str]:
    """List subject IDs that have a SimNIBS head-model (m2m) folder.

    Returns
    -------
    list of str
        Naturally sorted subject identifiers (without the ``sub-`` prefix).
        Returns an empty list if the SimNIBS directory does not exist.
    """
    simnibs_dir = self.simnibs() if self.project_dir else None
    if not simnibs_dir or not os.path.isdir(simnibs_dir):
        return []

    subjects = []
    for item in os.listdir(simnibs_dir):
        if not item.startswith(const.PREFIX_SUBJECT):
            continue
        sid = item.replace(const.PREFIX_SUBJECT, "", 1)
        if os.path.isdir(self.m2m(sid)):
            subjects.append(sid)

    subjects.sort(
        key=lambda x: [
            int(c) if c.isdigit() else c.lower() for c in re.split("([0-9]+)", x)
        ]
    )
    return subjects

list_simulations

list_simulations(sid: str) -> list[str]

List simulation folder names for a subject.

Parameters

sid : str Subject identifier.

Returns

list of str Alphabetically sorted simulation directory names. Returns an empty list if the Simulations/ directory does not exist.

Source code in tit/paths.py
def list_simulations(self, sid: str) -> list[str]:
    """List simulation folder names for a subject.

    Parameters
    ----------
    sid : str
        Subject identifier.

    Returns
    -------
    list of str
        Alphabetically sorted simulation directory names.  Returns an
        empty list if the ``Simulations/`` directory does not exist.
    """
    sim_root = self.simulations(sid)

    try:
        simulations: list[str] = []
        with os.scandir(sim_root) as it:
            for entry in it:
                if entry.is_dir() and not entry.name.startswith("."):
                    simulations.append(entry.name)
        simulations.sort()
        return simulations
    except OSError:
        return []

list_eeg_caps

list_eeg_caps(sid: str) -> list[str]

List EEG cap CSV filenames for a subject.

Parameters

sid : str Subject identifier.

Returns

list of str Sorted CSV filenames found in the eeg_positions/ directory.

Source code in tit/paths.py
def list_eeg_caps(self, sid: str) -> list[str]:
    """List EEG cap CSV filenames for a subject.

    Parameters
    ----------
    sid : str
        Subject identifier.

    Returns
    -------
    list of str
        Sorted CSV filenames found in the ``eeg_positions/`` directory.
    """
    eeg_pos_dir = self.eeg_positions(sid) if self.project_dir else None
    if not eeg_pos_dir or not os.path.isdir(eeg_pos_dir):
        return []

    caps = [
        f
        for f in os.listdir(eeg_pos_dir)
        if f.endswith(const.EXT_CSV) and not f.startswith(".")
    ]
    caps.sort()
    return caps

list_flex_search_runs

list_flex_search_runs(sid: str) -> list[str]

List flex-search run directories containing result metadata.

Only directories that contain flex_meta.json or electrode_positions.json are included.

Parameters

sid : str Subject identifier.

Returns

list of str Sorted run directory names.

Source code in tit/paths.py
def list_flex_search_runs(self, sid: str) -> list[str]:
    """List flex-search run directories containing result metadata.

    Only directories that contain ``flex_meta.json`` or
    ``electrode_positions.json`` are included.

    Parameters
    ----------
    sid : str
        Subject identifier.

    Returns
    -------
    list of str
        Sorted run directory names.
    """
    root = self.flex_search(sid) if self.project_dir else None
    if not root or not os.path.isdir(root):
        return []

    try:
        out: list[str] = []
        with os.scandir(root) as it:
            for entry in it:
                if not entry.is_dir() or entry.name.startswith("."):
                    continue
                if os.path.isfile(
                    os.path.join(entry.path, "flex_meta.json")
                ) or os.path.isfile(
                    os.path.join(entry.path, "electrode_positions.json")
                ):
                    out.append(entry.name)
        out.sort()
        return out
    except OSError:
        return []

spherical_analysis_name staticmethod

spherical_analysis_name(x: float, y: float, z: float, radius: float, coordinate_space: str) -> str

Build a canonical folder name for a spherical ROI analysis.

Parameters

x, y, z : float Centre coordinates of the sphere (mm). radius : float Sphere radius (mm). coordinate_space : str "MNI" or "subject".

Returns

str Folder name, e.g. "sphere_x0.00_y0.00_z0.00_r5.0_MNI".

Source code in tit/paths.py
@staticmethod
def spherical_analysis_name(
    x: float, y: float, z: float, radius: float, coordinate_space: str
) -> str:
    """Build a canonical folder name for a spherical ROI analysis.

    Parameters
    ----------
    x, y, z : float
        Centre coordinates of the sphere (mm).
    radius : float
        Sphere radius (mm).
    coordinate_space : str
        ``"MNI"`` or ``"subject"``.

    Returns
    -------
    str
        Folder name, e.g. ``"sphere_x0.00_y0.00_z0.00_r5.0_MNI"``.
    """
    coord_space_suffix = (
        "_MNI" if str(coordinate_space).upper() == "MNI" else "_subject"
    )
    return f"sphere_x{x:.2f}_y{y:.2f}_z{z:.2f}_r{float(radius)}{coord_space_suffix}"

cortical_analysis_name classmethod

cortical_analysis_name(*, whole_head: bool, region: str | None, atlas_name: str | None = None, atlas_path: str | None = None) -> str

Build a canonical folder name for a cortical/atlas analysis.

Parameters

whole_head : bool If True, the analysis covers the whole head (no region filter). region : str or None Atlas region label(s). Multiple regions are +-separated. atlas_name : str or None, optional Human-readable atlas name. atlas_path : str or None, optional Filesystem path to the atlas file (used as fallback for naming).

Returns

str Folder name, e.g. "cortical_precentral_DK40" or "whole_head_DK40".

Raises

ValueError If whole_head is False and region is empty or None.

Source code in tit/paths.py
@classmethod
def cortical_analysis_name(
    cls,
    *,
    whole_head: bool,
    region: str | None,
    atlas_name: str | None = None,
    atlas_path: str | None = None,
) -> str:
    """Build a canonical folder name for a cortical/atlas analysis.

    Parameters
    ----------
    whole_head : bool
        If *True*, the analysis covers the whole head (no region filter).
    region : str or None
        Atlas region label(s).  Multiple regions are ``+``-separated.
    atlas_name : str or None, optional
        Human-readable atlas name.
    atlas_path : str or None, optional
        Filesystem path to the atlas file (used as fallback for naming).

    Returns
    -------
    str
        Folder name, e.g. ``"cortical_precentral_DK40"`` or
        ``"whole_head_DK40"``.

    Raises
    ------
    ValueError
        If *whole_head* is *False* and *region* is empty or *None*.
    """
    atlas_clean = cls._atlas_name_clean(atlas_name or atlas_path or "unknown_atlas")
    if whole_head:
        return f"whole_head_{atlas_clean}"
    region_val = str(region or "").strip()
    if not region_val:
        raise ValueError(
            "region is required for cortical analysis unless whole_head=True"
        )
    if "+" in region_val:
        n = len(region_val.split("+"))
        h = hashlib.md5(region_val.encode()).hexdigest()[:8]
        return f"cortical_{n}regions_{atlas_clean}_{h}"
    return f"cortical_{region_val}_{atlas_clean}"

analysis_output_dir

analysis_output_dir(*, sid: str, sim: str, space: str, analysis_type: str, coordinates=None, radius=None, coordinate_space: str = 'subject', whole_head: bool = False, region: str | None = None, atlas_name: str | None = None, atlas_path: str | None = None) -> str

Return the analysis output directory path (does not create it).

Delegates to :meth:spherical_analysis_name or :meth:cortical_analysis_name depending on analysis_type.

Parameters

sid : str Subject identifier. sim : str Simulation name. space : str Analysis space ("mesh" or "voxel"). analysis_type : str "spherical" or "cortical". coordinates : sequence of float or None, optional (x, y, z) centre for spherical analysis. radius : float or None, optional Sphere radius in mm (required when analysis_type is "spherical"). coordinate_space : str, optional "MNI" or "subject". Default is "subject". whole_head : bool, optional Whether cortical analysis covers the whole head. region : str or None, optional Atlas region label(s) for cortical analysis. atlas_name : str or None, optional Atlas name for cortical analysis. atlas_path : str or None, optional Atlas file path for cortical analysis.

Returns

str Absolute path to the analysis output directory.

Raises

ValueError If required parameters for the chosen analysis_type are missing or invalid.

See Also

spherical_analysis_name : Naming convention for spherical ROIs. cortical_analysis_name : Naming convention for cortical/atlas ROIs.

Source code in tit/paths.py
def analysis_output_dir(
    self,
    *,
    sid: str,
    sim: str,
    space: str,
    analysis_type: str,
    coordinates=None,
    radius=None,
    coordinate_space: str = "subject",
    whole_head: bool = False,
    region: str | None = None,
    atlas_name: str | None = None,
    atlas_path: str | None = None,
) -> str:
    """Return the analysis output directory path (does not create it).

    Delegates to :meth:`spherical_analysis_name` or
    :meth:`cortical_analysis_name` depending on *analysis_type*.

    Parameters
    ----------
    sid : str
        Subject identifier.
    sim : str
        Simulation name.
    space : str
        Analysis space (``"mesh"`` or ``"voxel"``).
    analysis_type : str
        ``"spherical"`` or ``"cortical"``.
    coordinates : sequence of float or None, optional
        ``(x, y, z)`` centre for spherical analysis.
    radius : float or None, optional
        Sphere radius in mm (required when *analysis_type* is
        ``"spherical"``).
    coordinate_space : str, optional
        ``"MNI"`` or ``"subject"``.  Default is ``"subject"``.
    whole_head : bool, optional
        Whether cortical analysis covers the whole head.
    region : str or None, optional
        Atlas region label(s) for cortical analysis.
    atlas_name : str or None, optional
        Atlas name for cortical analysis.
    atlas_path : str or None, optional
        Atlas file path for cortical analysis.

    Returns
    -------
    str
        Absolute path to the analysis output directory.

    Raises
    ------
    ValueError
        If required parameters for the chosen *analysis_type* are
        missing or invalid.

    See Also
    --------
    spherical_analysis_name : Naming convention for spherical ROIs.
    cortical_analysis_name : Naming convention for cortical/atlas ROIs.
    """
    base = self.analysis_dir(sid, sim, space)
    at = str(analysis_type).lower()
    if at == "spherical":
        if not coordinates or len(coordinates) != 3 or radius is None:
            raise ValueError(
                "coordinates(3) and radius required for spherical analysis"
            )
        name = self.spherical_analysis_name(
            float(coordinates[0]),
            float(coordinates[1]),
            float(coordinates[2]),
            float(radius),
            coordinate_space,
        )
    else:
        name = self.cortical_analysis_name(
            whole_head=bool(whole_head),
            region=region,
            atlas_name=atlas_name,
            atlas_path=atlas_path,
        )
    return os.path.join(base, name)

tit.paths.get_path_manager

get_path_manager(project_dir: str | None = None) -> PathManager

Return the global :class:PathManager singleton.

Creates a new instance on the first call. If project_dir is provided, the singleton's :attr:~PathManager.project_dir is (re)set.

Parameters

project_dir : str or None, optional Project root directory. When None, the existing value (or environment auto-detection) is used.

Returns

PathManager The shared singleton instance.

See Also

reset_path_manager : Destroy the singleton for testing or re-init.

Source code in tit/paths.py
def get_path_manager(project_dir: str | None = None) -> PathManager:
    """Return the global :class:`PathManager` singleton.

    Creates a new instance on the first call.  If *project_dir* is provided,
    the singleton's :attr:`~PathManager.project_dir` is (re)set.

    Parameters
    ----------
    project_dir : str or None, optional
        Project root directory.  When *None*, the existing value (or
        environment auto-detection) is used.

    Returns
    -------
    PathManager
        The shared singleton instance.

    See Also
    --------
    reset_path_manager : Destroy the singleton for testing or re-init.
    """
    global _path_manager_instance
    if _path_manager_instance is None:
        _path_manager_instance = PathManager()
    if project_dir is not None:
        _path_manager_instance.project_dir = project_dir
    return _path_manager_instance

tit.paths.reset_path_manager

reset_path_manager() -> None

Destroy the singleton so the next call creates a fresh instance.

Primarily used in test fixtures to prevent cross-test contamination.

See Also

get_path_manager : Obtain the singleton instance.

Source code in tit/paths.py
def reset_path_manager() -> None:
    """Destroy the singleton so the next call creates a fresh instance.

    Primarily used in test fixtures to prevent cross-test contamination.

    See Also
    --------
    get_path_manager : Obtain the singleton instance.
    """
    global _path_manager_instance
    _path_manager_instance = None

Logging

tit.logger.setup_logging

setup_logging(level: str = 'INFO') -> None

Configure the tit logger hierarchy.

Sets the log level but adds no handlers — file handlers are attached later via :func:add_file_handler and GUI handlers via Qt signal bridges.

Parameters

level : str, optional Logging level name (e.g., "DEBUG", "INFO"). Default is "INFO".

See Also

add_file_handler : Attach a file handler to a named logger. add_stream_handler : Attach a console handler to a named logger.

Source code in tit/logger.py
def setup_logging(level: str = "INFO") -> None:
    """Configure the ``tit`` logger hierarchy.

    Sets the log level but adds **no** handlers — file handlers are attached
    later via :func:`add_file_handler` and GUI handlers via Qt signal bridges.

    Parameters
    ----------
    level : str, optional
        Logging level name (e.g., ``"DEBUG"``, ``"INFO"``).  Default is
        ``"INFO"``.

    See Also
    --------
    add_file_handler : Attach a file handler to a named logger.
    add_stream_handler : Attach a console handler to a named logger.
    """
    logger = logging.getLogger("tit")
    logger.handlers.clear()
    logger.setLevel(getattr(logging, level.upper(), logging.INFO))
    logger.propagate = False  # never bubble to root/terminal

    # Quiet noisy third-party loggers
    for name in ("matplotlib", "matplotlib.font_manager", "PIL"):
        logging.getLogger(name).setLevel(logging.ERROR)

tit.logger.add_file_handler

add_file_handler(log_file: str | Path, level: str = 'DEBUG', logger_name: str = 'tit') -> FileHandler

Attach a file handler to a named logger.

Creates the parent directory if it does not exist. Returns the handler so callers can remove it when the run completes.

Parameters

log_file : str or pathlib.Path Path to the log file (opened in append mode). level : str, optional Minimum log level for this handler. Default is "DEBUG" so the file captures everything. logger_name : str, optional Logger to attach to. Default is "tit" (the package root).

Returns

logging.FileHandler The newly created handler.

See Also

setup_logging : Set the package-wide log level. add_stream_handler : Attach a console (stdout) handler. get_file_only_logger : Create an isolated file-only logger.

Source code in tit/logger.py
def add_file_handler(
    log_file: str | Path,
    level: str = "DEBUG",
    logger_name: str = "tit",
) -> logging.FileHandler:
    """Attach a file handler to a named logger.

    Creates the parent directory if it does not exist.  Returns the handler
    so callers can remove it when the run completes.

    Parameters
    ----------
    log_file : str or pathlib.Path
        Path to the log file (opened in append mode).
    level : str, optional
        Minimum log level for this handler.  Default is ``"DEBUG"`` so the
        file captures everything.
    logger_name : str, optional
        Logger to attach to.  Default is ``"tit"`` (the package root).

    Returns
    -------
    logging.FileHandler
        The newly created handler.

    See Also
    --------
    setup_logging : Set the package-wide log level.
    add_stream_handler : Attach a console (stdout) handler.
    get_file_only_logger : Create an isolated file-only logger.
    """
    log_file = Path(log_file)
    log_file.parent.mkdir(parents=True, exist_ok=True)
    fh = logging.FileHandler(str(log_file), mode="a")
    fh.setLevel(getattr(logging, level.upper(), logging.DEBUG))
    fh.setFormatter(logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT))
    logging.getLogger(logger_name).addHandler(fh)
    return fh

tit.logger.add_stream_handler

add_stream_handler(logger_name: str = 'tit', level: str = 'INFO') -> StreamHandler

Attach a stdout handler to a named logger.

Used by scripts for terminal output and by __main__ entry points so that BaseProcessThread can capture subprocess stdout for the GUI.

Parameters

logger_name : str, optional Logger to attach to. Default is "tit". level : str, optional Minimum log level. Default is "INFO".

Returns

logging.StreamHandler The newly created handler.

See Also

setup_logging : Set the package-wide log level. add_file_handler : Attach a file handler.

Source code in tit/logger.py
def add_stream_handler(
    logger_name: str = "tit",
    level: str = "INFO",
) -> logging.StreamHandler:
    """Attach a stdout handler to a named logger.

    Used by scripts for terminal output and by ``__main__`` entry points
    so that ``BaseProcessThread`` can capture subprocess stdout for the GUI.

    Parameters
    ----------
    logger_name : str, optional
        Logger to attach to.  Default is ``"tit"``.
    level : str, optional
        Minimum log level.  Default is ``"INFO"``.

    Returns
    -------
    logging.StreamHandler
        The newly created handler.

    See Also
    --------
    setup_logging : Set the package-wide log level.
    add_file_handler : Attach a file handler.
    """
    import sys

    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(getattr(logging, level.upper(), logging.INFO))
    handler.setFormatter(logging.Formatter("%(message)s"))
    logger = logging.getLogger(logger_name)
    logger.addHandler(handler)
    return handler

tit.logger.get_file_only_logger

get_file_only_logger(name: str, log_file: str | Path, level: str = 'DEBUG') -> Logger

Return a logger that writes only to log_file — no console output.

If a logger with name already exists its handlers are replaced so that repeated calls (e.g., across ROIs) always point at the correct file.

Parameters

name : str Logger name (should be unique per use-case). log_file : str or pathlib.Path Path to the log file. level : str, optional Minimum log level. Default is "DEBUG".

Returns

logging.Logger A configured logger with a single file handler and propagate=False.

See Also

add_file_handler : Lower-level helper used internally.

Source code in tit/logger.py
def get_file_only_logger(
    name: str,
    log_file: str | Path,
    level: str = "DEBUG",
) -> logging.Logger:
    """Return a logger that writes **only** to *log_file* — no console output.

    If a logger with *name* already exists its handlers are replaced so that
    repeated calls (e.g., across ROIs) always point at the correct file.

    Parameters
    ----------
    name : str
        Logger name (should be unique per use-case).
    log_file : str or pathlib.Path
        Path to the log file.
    level : str, optional
        Minimum log level.  Default is ``"DEBUG"``.

    Returns
    -------
    logging.Logger
        A configured logger with a single file handler and
        ``propagate=False``.

    See Also
    --------
    add_file_handler : Lower-level helper used internally.
    """
    logger = logging.getLogger(name)
    logger.handlers.clear()
    logger.setLevel(getattr(logging, level.upper(), logging.DEBUG))
    logger.propagate = False  # never bubble to root/terminal
    add_file_handler(log_file, level=level, logger_name=name)
    return logger

Config IO

tit.config_io.serialize_config

serialize_config(config: Any) -> dict[str, Any]

Convert a config dataclass to a JSON-serialisable dict.

Handles Enum fields (via .value), nested dataclasses (recursed), union-typed ROI / electrode specs (injects a _type discriminator), and None values (preserved as JSON null).

Also injects project_dir from the active :class:~tit.paths.PathManager so that subprocess entry points can initialise their own singleton.

Parameters

config : dataclass instance Any config dataclass (e.g., FlexConfig, ExConfig).

Returns

dict JSON-serialisable dictionary representation of config.

See Also

write_config_json : Serialise and write to a temp file in one step. read_config_json : Read a JSON config back into a dict.

Source code in tit/config_io.py
def serialize_config(config: Any) -> dict[str, Any]:
    """Convert a config dataclass to a JSON-serialisable dict.

    Handles Enum fields (via ``.value``), nested dataclasses (recursed),
    union-typed ROI / electrode specs (injects a ``_type`` discriminator),
    and *None* values (preserved as JSON ``null``).

    Also injects ``project_dir`` from the active :class:`~tit.paths.PathManager`
    so that subprocess entry points can initialise their own singleton.

    Parameters
    ----------
    config : dataclass instance
        Any config dataclass (e.g., ``FlexConfig``, ``ExConfig``).

    Returns
    -------
    dict
        JSON-serialisable dictionary representation of *config*.

    See Also
    --------
    write_config_json : Serialise and write to a temp file in one step.
    read_config_json : Read a JSON config back into a dict.
    """
    data = _serialize(config)
    # Inject project_dir for subprocess entry points
    from tit.paths import get_path_manager

    data["project_dir"] = get_path_manager().project_dir
    return data

tit.config_io.write_config_json

write_config_json(config: Any, prefix: str = 'config') -> str

Serialise a config dataclass to a temporary JSON file.

Parameters

config : dataclass instance Config object to serialise. prefix : str, optional Filename prefix for the temp file. Default is "config".

Returns

str Absolute path to the created JSON file.

See Also

serialize_config : Convert to dict without writing to disk. read_config_json : Read a JSON config file.

Source code in tit/config_io.py
def write_config_json(config: Any, prefix: str = "config") -> str:
    """Serialise a config dataclass to a temporary JSON file.

    Parameters
    ----------
    config : dataclass instance
        Config object to serialise.
    prefix : str, optional
        Filename prefix for the temp file.  Default is ``"config"``.

    Returns
    -------
    str
        Absolute path to the created JSON file.

    See Also
    --------
    serialize_config : Convert to dict without writing to disk.
    read_config_json : Read a JSON config file.
    """
    data = serialize_config(config)
    fd, path = tempfile.mkstemp(prefix=f"{prefix}_", suffix=".json")
    with os.fdopen(fd, "w") as f:
        json.dump(data, f, indent=2)
    return path

tit.config_io.read_config_json

read_config_json(path: str) -> dict[str, Any]

Read a JSON config file and return the parsed dict.

Parameters

path : str Path to the JSON file.

Returns

dict Parsed JSON contents.

See Also

write_config_json : Create a config JSON file from a dataclass.

Source code in tit/config_io.py
def read_config_json(path: str) -> dict[str, Any]:
    """Read a JSON config file and return the parsed dict.

    Parameters
    ----------
    path : str
        Path to the JSON file.

    Returns
    -------
    dict
        Parsed JSON contents.

    See Also
    --------
    write_config_json : Create a config JSON file from a dataclass.
    """
    with open(path) as f:
        return json.load(f)

Exceptions

tit.pre.utils.PreprocessError

Bases: RuntimeError

Raised when a preprocessing step fails.

See Also

PreprocessCancelled : Raised specifically when cancelled by a stop event.

tit.pre.utils.PreprocessCancelled

Bases: RuntimeError

Raised when a preprocessing run is cancelled via a stop event.

See Also

PreprocessError : General preprocessing failure.

tit.pre.qsi.docker_builder.DockerBuildError

Bases: Exception

Raised when Docker command construction fails.