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.fastsurfer() |
<project>/derivatives/fastsurfer |
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.fastsurfer_subject("001") |
<fastsurfer>/sub-001 |
pm.fastsurfer_mri("001") |
<fastsurfer>/sub-001/mri |
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
The tit logger hierarchy has propagate=False, isolating it from the root logger. Importing tit attaches its own stdout handler; pipeline modules additionally attach file handlers. Calling setup_logging() alone does not add handlers.
| 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...")
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 (mTI_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. The job server uses this mechanism to pass configurations to pipeline subprocesses.
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 import run_pipeline
from tit.pre.utils import PreprocessError, PreprocessCancelled
try:
run_pipeline(subject_ids=["001"], create_m2m=True)
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, FastSurfer 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 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
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
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
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")
|
fastsurfer
Path to <project>/derivatives/fastsurfer/.
Source code in tit/paths.py
| def fastsurfer(self) -> str:
"""Path to ``<project>/derivatives/fastsurfer/``."""
return os.path.join(self._root(), "derivatives", "fastsurfer")
|
freesurfer
Path to <project>/derivatives/freesurfer/ (legacy, read-only).
TI-Toolbox no longer writes here -- FastSurfer --seg_only
replaced recon-all -- but every atlas reader still discovers
existing recon-all output so projects processed with older
releases keep working.
Source code in tit/paths.py
| def freesurfer(self) -> str:
"""Path to ``<project>/derivatives/freesurfer/`` (legacy, read-only).
TI-Toolbox no longer writes here -- FastSurfer ``--seg_only``
replaced ``recon-all`` -- but every atlas reader still discovers
existing ``recon-all`` output so projects processed with older
releases keep working.
"""
return os.path.join(self._root(), "derivatives", "freesurfer")
|
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
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")
|
jobs_dir
Path to <project>/code/ti-toolbox/jobs/ (job server state).
Source code in tit/paths.py
| def jobs_dir(self) -> str:
"""Path to ``<project>/code/ti-toolbox/jobs/`` (job server state)."""
return os.path.join(self._root(), "code", "ti-toolbox", "jobs")
|
dot_dir
Path to <project>/.ti-toolbox/ (not created).
Source code in tit/paths.py
| def dot_dir(self) -> str:
"""Path to ``<project>/.ti-toolbox/`` (not created)."""
return dot_dir(self._root())
|
cache_root
Path to <project>/.ti-toolbox/cache/ (not created).
Source code in tit/paths.py
| def cache_root(self) -> str:
"""Path to ``<project>/.ti-toolbox/cache/`` (not created)."""
return cache_dir(self._root())
|
cache
Path to <project>/.ti-toolbox/cache/<parts...> (not created).
Source code in tit/paths.py
| def cache(self, *parts: str) -> str:
"""Path to ``<project>/.ti-toolbox/cache/<parts...>`` (not created)."""
return cache_dir(self._root(), *parts)
|
ensure_cache
ensure_cache(*parts: str) -> str
Create and return a cache directory, migrating legacy locations once.
Source code in tit/paths.py
| def ensure_cache(self, *parts: str) -> str:
"""Create and return a cache directory, migrating legacy locations once."""
return ensure_cache_dir(self._root(), *parts)
|
scene_cache
Path to the scene-payload cache for sid (cache/scene/sub-<id>/).
Source code in tit/paths.py
| def scene_cache(self, sid: str) -> str:
"""Path to the scene-payload cache for *sid* (``cache/scene/sub-<id>/``)."""
return self.cache("scene", f"sub-{validate_subject_id(sid)}")
|
mask_cache
Path to the prepared-ROI-mask cache for sid (cache/masks/sub-<id>/).
Source code in tit/paths.py
| def mask_cache(self, sid: str) -> str:
"""Path to the prepared-ROI-mask cache for *sid* (``cache/masks/sub-<id>/``)."""
return self.cache("masks", f"sub-{validate_subject_id(sid)}")
|
viewer_stats_cache
viewer_stats_cache() -> str
Path to the viewer's per-volume statistics sidecars (cache/stats/).
Source code in tit/paths.py
| def viewer_stats_cache(self) -> str:
"""Path to the viewer's per-volume statistics sidecars (``cache/stats/``)."""
return self.cache("stats")
|
storage_cache
Path to the project disk-usage scan (cache/storage/storage.json).
Source code in tit/paths.py
| def storage_cache(self) -> str:
"""Path to the project disk-usage scan (``cache/storage/storage.json``)."""
return os.path.join(self.cache("storage"), "storage.json")
|
montage_config
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
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
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
def _usable_dir(path: str | None) -> str | None:
if not path:
return None
try:
os.makedirs(path, exist_ok=True)
except OSError:
return None
return path if os.path.isdir(path) else None
# Inside Docker the Electron launcher mounts the host config here.
# Prefer the in-container mount when present; TIT_USER_CONFIG is a
# host-side path used by docker-compose and may not exist in-container.
docker_path = _usable_dir(const.USER_CONFIG_CONTAINER_PATH)
if docker_path:
return docker_path
# Explicit override for non-standard/container-less launches.
env_path = _usable_dir(os.environ.get("TIT_USER_CONFIG"))
if env_path:
return env_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
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
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 self._under(
self.ti_toolbox(),
"stats",
validate_name(analysis_type, "analysis type"),
validate_name(analysis_name, "analysis name"),
)
|
logs_group
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
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
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
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 self._under(self.simnibs(), f"sub-{validate_subject_id(sid)}")
|
m2m
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 self._under(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
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")
|
masks
Path to the user-supplied custom mask directory for sid.
Any integer label volume placed here (*.nii.gz/*.nii/*.mgz,
in the subject space m2m_{sid} was built from) is auto-discovered as
a targetable volume atlas by the optimiser ROI pickers.
Source code in tit/paths.py
| def masks(self, sid: str) -> str:
"""Path to the user-supplied custom mask directory for *sid*.
Any integer label volume placed here (``*.nii.gz``/``*.nii``/``*.mgz``,
in the subject space ``m2m_{sid}`` was built from) is auto-discovered as
a targetable volume atlas by the optimiser ROI pickers.
"""
return os.path.join(self.m2m(sid), "masks")
|
t1
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
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
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")
|
forward
Path to the EEG source-forward directory for sid.
Holds the MNE forward solution, source space, head<->MRI transform,
fsaverage morph, and the point-electrode leadfield they derive from
(derivatives/SimNIBS/sub-{sid}/forward/). Distinct from
:meth:leadfields, which the optimizer uses for modeled-electrode
stimulation leadfields.
Source code in tit/paths.py
| def forward(self, sid: str) -> str:
"""Path to the EEG source-forward directory for *sid*.
Holds the MNE forward solution, source space, head<->MRI transform,
fsaverage morph, and the point-electrode leadfield they derive from
(``derivatives/SimNIBS/sub-{sid}/forward/``). Distinct from
:meth:`leadfields`, which the optimizer uses for modeled-electrode
stimulation leadfields.
"""
return os.path.join(self.sub(sid), "forward")
|
simulations
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
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 self._under(self.ti_toolbox(), "logs", f"sub-{validate_subject_id(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 self._under(
self.ti_toolbox(), "tissue_analysis", f"sub-{validate_subject_id(sid)}"
)
|
bids_subject
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 self._under(self._root(), f"sub-{validate_subject_id(sid)}")
|
bids_datatype
bids_datatype(sid: str, datatype: str) -> str
Path to <project>/sub-{sid}/{datatype}/ for any BIDS datatype.
Source code in tit/paths.py
| def bids_datatype(self, sid: str, datatype: str) -> str:
"""Path to ``<project>/sub-{sid}/{datatype}/`` for any BIDS datatype."""
return self._under(self.bids_subject(sid), validate_name(datatype, "datatype"))
|
bids_anat
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 self.bids_datatype(sid, "anat")
|
bids_dwi
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 self.bids_datatype(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 self._under(self.sourcedata(), f"sub-{validate_subject_id(sid)}")
|
fastsurfer_subject
fastsurfer_subject(sid: str) -> str
Path to derivatives/fastsurfer/sub-{sid}/.
Source code in tit/paths.py
| def fastsurfer_subject(self, sid: str) -> str:
"""Path to ``derivatives/fastsurfer/sub-{sid}/``."""
return self._under(self.fastsurfer(), f"sub-{validate_subject_id(sid)}")
|
fastsurfer_mri
fastsurfer_mri(sid: str) -> str
Path to derivatives/fastsurfer/sub-{sid}/mri/.
Holds aparc.DKTatlas+aseg.deep.mgz (plus the .nii.gz copy and
the *_labels.txt sidecar :mod:tit.pre.fastsurfer writes).
Source code in tit/paths.py
| def fastsurfer_mri(self, sid: str) -> str:
"""Path to ``derivatives/fastsurfer/sub-{sid}/mri/``.
Holds ``aparc.DKTatlas+aseg.deep.mgz`` (plus the ``.nii.gz`` copy and
the ``*_labels.txt`` sidecar :mod:`tit.pre.fastsurfer` writes).
"""
return os.path.join(self.fastsurfer_subject(sid), "mri")
|
freesurfer_subject
freesurfer_subject(sid: str) -> str
Path to derivatives/freesurfer/sub-{sid}/ (legacy, read-only).
Source code in tit/paths.py
| def freesurfer_subject(self, sid: str) -> str:
"""Path to ``derivatives/freesurfer/sub-{sid}/`` (legacy, read-only)."""
return self._under(self.freesurfer(), f"sub-{validate_subject_id(sid)}")
|
freesurfer_mri
freesurfer_mri(sid: str) -> str
Path to derivatives/freesurfer/sub-{sid}/mri/ (legacy, read-only).
Source code in tit/paths.py
| def freesurfer_mri(self, sid: str) -> str:
"""Path to ``derivatives/freesurfer/sub-{sid}/mri/`` (legacy, read-only)."""
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 self._under(self.qsiprep(), f"sub-{validate_subject_id(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 self._under(self.qsirecon(), f"sub-{validate_subject_id(sid)}")
|
ex_search
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")
|
m_ex_search
Path to multipolar exhaustive-search results for sid.
Source code in tit/paths.py
| def m_ex_search(self, sid: str) -> str:
"""Path to multipolar exhaustive-search results for *sid*."""
return os.path.join(self.sub(sid), "m-ex-search")
|
flex_search
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
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 self._under(self.simulations(sid), validate_name(sim, "simulation"))
|
sim_fsaverage
Path to the fsaverage field-map cache for a simulation.
derivatives/SimNIBS/sub-{sid}/Simulations/{sim}/fsaverage/ -- holds
the .npz field projection produced by
:func:tit.source.project_fields_to_fsaverage. Co-located with the
simulation it derives from (and mirroring SimNIBS's native
map_to_fsavg layout), a sibling of TI/ and high_Frequency/ --
distinct from :meth:forward, which is EEG source reconstruction.
Source code in tit/paths.py
| def sim_fsaverage(self, sid: str, sim: str) -> str:
"""Path to the fsaverage field-map cache for a simulation.
``derivatives/SimNIBS/sub-{sid}/Simulations/{sim}/fsaverage/`` -- holds
the ``.npz`` field projection produced by
:func:`tit.source.project_fields_to_fsaverage`. Co-located with the
simulation it derives from (and mirroring SimNIBS's native
``map_to_fsavg`` layout), a sibling of ``TI/`` and ``high_Frequency/`` --
distinct from :meth:`forward`, which is EEG source reconstruction.
"""
return os.path.join(self.simulation(sid, sim), "fsaverage")
|
ti_mesh
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 self._under(self.simulation(sid, sim), "TI", "mesh", f"{sim}_TI.msh")
|
ti_mesh_dir
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 self._under(
self.simulation(sid, sim), "TI", "mesh", "surfaces", f"{sim}_TI_central.msh"
)
|
mti_central_surface
mti_central_surface(sid: str, sim: str) -> str
Path to the mTI central cortical surface mesh.
mTI runs write their own central surface under mTI/mesh/surfaces/;
the TI/mesh/surfaces/ copy is written only by 2-pair TI runs.
Source code in tit/paths.py
| def mti_central_surface(self, sid: str, sim: str) -> str:
"""Path to the mTI central cortical surface mesh.
mTI runs write their own central surface under ``mTI/mesh/surfaces/``;
the ``TI/mesh/surfaces/`` copy is written only by 2-pair TI runs.
"""
return self._under(
self.simulation(sid, sim),
"mTI",
"mesh",
"surfaces",
f"{sim}_mTI_central.msh",
)
|
mti_mesh_dir
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
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 self._under(
self.sourcedata_subject(sid), validate_name(modality, "modality"), "dicom"
)
|
ex_search_run
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 self._under(self.ex_search(sid), validate_name(run, "run"))
|
m_ex_search_run
Path to a specific multipolar exhaustive-search run directory.
Source code in tit/paths.py
| def m_ex_search_run(self, sid: str, run: str) -> str:
"""Path to a specific multipolar exhaustive-search run directory."""
return self._under(self.m_ex_search(sid), validate_name(run, "run"))
|
flex_search_run
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 self._under(self.flex_search(sid), validate_name(name, "run"))
|
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
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
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_bids_subjects
Subject ids with a raw BIDS folder (<project>/sub-*), naturally sorted.
Source code in tit/paths.py
| def list_bids_subjects(self) -> list[str]:
"""Subject ids with a raw BIDS folder (``<project>/sub-*``), naturally sorted."""
if not self.project_dir:
return []
return self._list_sub_dirs(self.project_dir)
|
list_fastsurfer_subjects
list_fastsurfer_subjects() -> list[str]
Subject ids with a derivatives/fastsurfer/sub-* folder, naturally sorted.
Source code in tit/paths.py
| def list_fastsurfer_subjects(self) -> list[str]:
"""Subject ids with a ``derivatives/fastsurfer/sub-*`` folder, naturally sorted."""
if not self.project_dir:
return []
return self._list_sub_dirs(self.fastsurfer())
|
list_freesurfer_subjects
list_freesurfer_subjects() -> list[str]
Subject ids with a derivatives/freesurfer/sub-* folder (legacy).
Kept so projects carrying old recon-all output still list those
subjects; nothing in the toolbox writes this tree any more.
Source code in tit/paths.py
| def list_freesurfer_subjects(self) -> list[str]:
"""Subject ids with a ``derivatives/freesurfer/sub-*`` folder (legacy).
Kept so projects carrying old ``recon-all`` output still list those
subjects; nothing in the toolbox writes this tree any more.
"""
if not self.project_dir:
return []
return self._list_sub_dirs(self.freesurfer())
|
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.
"""
if not self.project_dir:
return []
return [
sid
for sid in self._list_sub_dirs(self.simnibs())
if os.path.isdir(self.m2m(sid))
]
|
list_simulations
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 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 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
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}"
|
spherical_union_analysis_name
classmethod
spherical_union_analysis_name(spheres, coordinate_space: str) -> str
Build a folder name for a union of several spherical ROIs.
Parameters
spheres : sequence of tuple of float
One (x, y, z, r) per sphere.
coordinate_space : str
"MNI" or "subject".
Returns
str
Folder name, e.g. "spheres2_x-40.00_y-20.00_z5.00_r5.0+
x40.00_y-20.00_z5.00_r5.0_subject". Long unions are shortened
to the first sphere plus a hash of the rest so the name stays
within filesystem limits.
Source code in tit/paths.py
| @classmethod
def spherical_union_analysis_name(cls, spheres, coordinate_space: str) -> str:
"""Build a folder name for a union of several spherical ROIs.
Parameters
----------
spheres : sequence of tuple of float
One ``(x, y, z, r)`` per sphere.
coordinate_space : str
``"MNI"`` or ``"subject"``.
Returns
-------
str
Folder name, e.g. ``"spheres2_x-40.00_y-20.00_z5.00_r5.0+
x40.00_y-20.00_z5.00_r5.0_subject"``. Long unions are shortened
to the first sphere plus a hash of the rest so the name stays
within filesystem limits.
"""
parts = [
f"x{float(x):.2f}_y{float(y):.2f}_z{float(z):.2f}_r{float(r)}"
for x, y, z, r in spheres
]
suffix = "_MNI" if str(coordinate_space).upper() == "MNI" else "_subject"
body = "+".join(parts)
name = f"spheres{len(parts)}_{body}{suffix}"
if len(name) > 150:
digest = hashlib.sha1(body.encode()).hexdigest()[:8]
name = f"spheres{len(parts)}_{parts[0]}_and{len(parts) - 1}more_{digest}{suffix}"
return name
|
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', spheres=None, 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".
spheres : sequence of tuple of float or None, optional
One (x, y, z, r) per sphere when several spheres are unioned
into a single ROI. Takes precedence over coordinates/radius.
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",
spheres=None,
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"``.
spheres : sequence of tuple of float or None, optional
One ``(x, y, z, r)`` per sphere when several spheres are unioned
into a single ROI. Takes precedence over *coordinates*/*radius*.
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 spheres:
return os.path.join(
base, self.spherical_union_analysis_name(spheres, coordinate_space)
)
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
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 (the BIDS root holding sub-* and
derivatives/). Must exist. When None (default), the
existing value is kept; if none was set, it is auto-detected from
the PROJECT_DIR environment variable or, inside the container,
/mnt/<PROJECT_DIR_NAME> -- which is why scripts and notebooks
run in the app need no argument.
Returns
PathManager
The shared singleton instance. Every path accessor
(pm.m2m(sid), pm.simulations(sid), ...) raises
RuntimeError("Project directory not set") if no project root
could be resolved.
Raises
ValueError
If project_dir is given but is not an existing directory.
Examples
from tit import get_path_manager
pm = get_path_manager("/data/project") # doctest: +SKIP
pm.list_simnibs_subjects() # doctest: +SKIP
['ernie', '101']
pm.m2m("ernie") # doctest: +SKIP
'/data/project/derivatives/SimNIBS/sub-ernie/m2m_ernie'
get_path_manager() is pm # the same singleton on later calls # doctest: +SKIP
True
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 (the BIDS root holding ``sub-*`` and
``derivatives/``). Must exist. When ``None`` (default), the
existing value is kept; if none was set, it is auto-detected from
the ``PROJECT_DIR`` environment variable or, inside the container,
``/mnt/<PROJECT_DIR_NAME>`` -- which is why scripts and notebooks
run in the app need no argument.
Returns
-------
PathManager
The shared singleton instance. Every path accessor
(``pm.m2m(sid)``, ``pm.simulations(sid)``, ...) raises
``RuntimeError("Project directory not set")`` if no project root
could be resolved.
Raises
------
ValueError
If *project_dir* is given but is not an existing directory.
Examples
--------
>>> from tit import get_path_manager
>>> pm = get_path_manager("/data/project") # doctest: +SKIP
>>> pm.list_simnibs_subjects() # doctest: +SKIP
['ernie', '101']
>>> pm.m2m("ernie") # doctest: +SKIP
'/data/project/derivatives/SimNIBS/sub-ernie/m2m_ernie'
>>> get_path_manager() is pm # the same singleton on later calls # doctest: +SKIP
True
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 of its own — file handlers
are attached later via :func:add_file_handler and GUI handlers via Qt
signal bridges. The one exception: when $TIT_EVENTS_FILE is set, a
:class:JsonEventHandler is attached to both the tit and
simnibs loggers (flex/ex/mex/sim already attach their own file
handlers to simnibs -- that is where FEM solver progress goes) so
every log record also becomes a type: "log" line in the job's
events.jsonl, with no per-runner changes needed for plain log
output. Structured events (stage/progress/artifact/result/exit) are
emitted separately via tit.jobs.events, through the same sink.
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.
get_event_sink : The sink a JsonEventHandler writes through.
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 of its own — file handlers
are attached later via :func:`add_file_handler` and GUI handlers via Qt
signal bridges. The one exception: when ``$TIT_EVENTS_FILE`` is set, a
:class:`JsonEventHandler` is attached to both the ``tit`` and
``simnibs`` loggers (flex/ex/mex/sim already attach their own file
handlers to ``simnibs`` -- that is where FEM solver progress goes) so
every log record also becomes a ``type: "log"`` line in the job's
``events.jsonl``, with no per-runner changes needed for plain log
output. Structured events (stage/progress/artifact/result/exit) are
emitted separately via ``tit.jobs.events``, through the same sink.
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.
get_event_sink : The sink a JsonEventHandler writes through.
"""
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)
sink = get_event_sink()
if sink is not None:
for logger_name in ("tit", "simnibs"):
target = logging.getLogger(logger_name)
if not any(isinstance(h, JsonEventHandler) for h in target.handlers):
handler = JsonEventHandler(sink)
handler.setLevel(logging.DEBUG)
target.addHandler(handler)
else:
logging.getLogger("tit.logger").debug(
f"${TIT_EVENTS_FILE_ENV} not set; no JsonEventHandler attached"
)
|
tit.logger.add_file_handler
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 handler for log_file on logger_name -- newly created, or the
existing one if this exact (logger, path) pair was already attached
(a long-lived process, e.g. one ROI loop calling this per iteration,
would otherwise leak one handler -- and one open file descriptor --
per call).
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 handler for *log_file* on *logger_name* -- newly created, or the
existing one if this exact (logger, path) pair was already attached
(a long-lived process, e.g. one ROI loop calling this per iteration,
would otherwise leak one handler -- and one open file descriptor --
per call).
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)
logger = logging.getLogger(logger_name)
resolved = str(log_file.resolve())
for existing in logger.handlers:
if (
isinstance(existing, logging.FileHandler)
and getattr(existing, "baseFilename", None) == resolved
):
return existing
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))
logger.addHandler(fh)
return fh
|
tit.logger.add_stream_handler
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
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
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 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.