Optimization¶
TI-Toolbox provides two optimization strategies for finding optimal electrode placements: flex-search (differential evolution) for continuous optimization and exhaustive search for discrete combinatorial search.
graph LR
ROI[Target ROI] --> OPT{Strategy}
OPT -->|continuous| FLEX[Flex-Search]
OPT -->|discrete| EX[Ex-Search]
FLEX --> RESULT[Optimal Montage]
EX --> RESULT
RESULT -->|simulate| SIM[Simulation]
style RESULT fill:#2d5a27,stroke:#4a8,color:#fff
Flex-Search (Differential Evolution)¶
Flex-search uses differential evolution to find optimal electrode positions on the EEG cap. It explores the continuous space of all possible electrode combinations.
from tit.opt import FlexConfig, run_flex_search
config = FlexConfig(
subject_id="001",
project_dir="/data/my_project",
goal="mean", # "mean", "max", or "focality"
postproc="max_TI", # "max_TI", "dir_TI_normal", "dir_TI_tangential"
current_mA=1.0,
electrode=FlexConfig.ElectrodeConfig(shape="ellipse", dimensions=[8.0, 8.0]),
roi=FlexConfig.SphericalROI(x=-42, y=-20, z=55, radius=10, use_mni=True),
eeg_net="GSN-HydroCel-185",
n_multistart=3,
)
result = run_flex_search(config)
print(f"Best value: {result.best_value}")
print(f"Output: {result.output_folder}")
Optimization Goals¶
| Goal | Description |
|---|---|
"mean" |
Maximize mean field intensity within the ROI |
"max" |
Maximize peak field intensity within the ROI |
"focality" |
Maximize the ratio of ROI intensity to whole-brain intensity |
ROI Types¶
Multi-start
Use n_multistart to run multiple optimization restarts with different initial conditions. This helps avoid local optima. A value of 3-5 is usually sufficient.
Exhaustive Search¶
Exhaustive search tests all possible electrode combinations from a predefined pool. This is useful when you want to find the best combination from a specific set of electrodes.
from tit.opt import ExConfig, run_ex_search
config = ExConfig(
subject_id="001",
project_dir="/data/my_project",
leadfield_hdf="leadfield.hdf5", # filename within the leadfields directory
roi_name="motor_roi",
electrodes=ExConfig.PoolElectrodes(electrodes=["C3", "C4", "F3", "F4", "P3", "P4"]),
)
result = run_ex_search(config)
print(f"Combinations tested: {result.n_combinations}")
print(f"Results CSV: {result.results_csv}")
You can also use bucket electrodes to specify separate pools for each channel position:
config = ExConfig(
subject_id="001",
project_dir="/data/my_project",
leadfield_hdf="leadfield.hdf5",
roi_name="motor_roi",
electrodes=ExConfig.BucketElectrodes(
e1_plus=["C3", "C1"],
e1_minus=["C4", "C2"],
e2_plus=["F3", "F1"],
e2_minus=["F4", "F2"],
),
)
Leadfield Prerequisite
Exhaustive search requires a pre-computed leadfield matrix. Generate one using tit.opt.leadfield before running the search.
Leadfield Generation¶
The leadfield matrix maps electrode currents to brain fields and is required for exhaustive search. Use the LeadfieldGenerator class:
from tit.opt.leadfield import LeadfieldGenerator
generator = LeadfieldGenerator(
subject_id="001",
electrode_cap="GSN-HydroCel-185",
)
# Generate a new leadfield (requires SimNIBS)
leadfield_path = generator.generate()
# List available leadfields for the subject
available = generator.list_leadfields()
for net_name, hdf5_path, size_gb in available:
print(f"{net_name}: {hdf5_path} ({size_gb:.2f} GB)")
# Get electrode names from a cap
electrodes = generator.get_electrode_names()
API Reference¶
Flex-Search¶
tit.opt.config.FlexConfig
dataclass
¶
FlexConfig(subject_id: str, project_dir: str, goal: OptGoal, postproc: FieldPostproc, current_mA: float, electrode: ElectrodeConfig, roi: SphericalROI | AtlasROI | SubcorticalROI, anisotropy_type: str = 'scalar', aniso_maxratio: float = 10.0, aniso_maxcond: float = 2.0, non_roi_method: NonROIMethod | None = None, non_roi: SphericalROI | AtlasROI | SubcorticalROI | None = None, thresholds: str | None = None, eeg_net: str | None = None, enable_mapping: bool = False, disable_mapping_simulation: bool = False, output_folder: str | None = None, run_final_electrode_simulation: bool = False, n_multistart: int = 1, max_iterations: int | None = None, population_size: int | None = None, tolerance: float | None = None, mutation: str | None = None, recombination: float | None = None, cpus: int | None = None, detailed_results: bool = False, visualize_valid_skin_region: bool = False, skin_visualization_net: str | None = None)
Full configuration for flex-search optimization.
SphericalROI
dataclass
¶
Spherical region of interest defined by center + radius.
AtlasROI
dataclass
¶
Cortical surface ROI from a FreeSurfer annotation atlas.
SubcorticalROI
dataclass
¶
Subcortical volume ROI from a volumetric atlas.
ElectrodeConfig
dataclass
¶
ElectrodeConfig(shape: str = 'ellipse', dimensions: list[float] = (lambda: [8.0, 8.0])(), gel_thickness: float = 4.0)
Electrode geometry for flex-search.
Only gel_thickness is needed here — the optimization leadfield uses point electrodes; gel_thickness is recorded in the manifest for downstream simulation.
tit.opt.flex.flex.run_flex_search ¶
run_flex_search(config: FlexConfig) -> FlexResult
Run flex-search optimization from a typed FlexConfig.
Source code in tit/opt/flex/flex.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
tit.opt.config.FlexConfig.SphericalROI
dataclass
¶
Spherical region of interest defined by center + radius.
tit.opt.config.FlexConfig.AtlasROI
dataclass
¶
Cortical surface ROI from a FreeSurfer annotation atlas.
tit.opt.config.FlexConfig.SubcorticalROI
dataclass
¶
Subcortical volume ROI from a volumetric atlas.
tit.opt.config.FlexConfig.ElectrodeConfig
dataclass
¶
ElectrodeConfig(shape: str = 'ellipse', dimensions: list[float] = (lambda: [8.0, 8.0])(), gel_thickness: float = 4.0)
Electrode geometry for flex-search.
Only gel_thickness is needed here — the optimization leadfield uses point electrodes; gel_thickness is recorded in the manifest for downstream simulation.
tit.opt.config.FlexResult
dataclass
¶
FlexResult(success: bool, output_folder: str, function_values: list[float], best_value: float, best_run_index: int)
Result from a flex-search optimization run.
Exhaustive Search¶
tit.opt.config.ExConfig
dataclass
¶
ExConfig(subject_id: str, project_dir: str, leadfield_hdf: str, roi_name: str, electrodes: BucketElectrodes | PoolElectrodes, total_current: float = 2.0, current_step: float = 0.5, channel_limit: float | None = None, roi_radius: float = 3.0, run_name: str | None = None)
Full configuration for exhaustive search optimization.
BucketElectrodes
dataclass
¶
Separate electrode lists for each bipolar channel position.
tit.opt.ex.ex.run_ex_search ¶
Run exhaustive search from a typed config object.
Source code in tit/opt/ex/ex.py
tit.opt.config.ExConfig.PoolElectrodes
dataclass
¶
Single electrode pool — all positions draw from the same set.
tit.opt.config.ExConfig.BucketElectrodes
dataclass
¶
Separate electrode lists for each bipolar channel position.
tit.opt.config.ExResult
dataclass
¶
ExResult(success: bool, output_dir: str, n_combinations: int, results_csv: str | None = None, config_json: str | None = None)
Result from an exhaustive search run.
Leadfield¶
tit.opt.leadfield.LeadfieldGenerator ¶
LeadfieldGenerator(subject_id: str, electrode_cap: str = 'EEG10-10', progress_callback: Callable | None = None, termination_flag: Callable[[], bool] | None = None)
Generate and list leadfield matrices for TI optimization.
Initialize leadfield generator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject_id
|
str
|
Subject identifier (e.g. "101"). |
required |
electrode_cap
|
str
|
EEG cap name without .csv (e.g. "GSN-HydroCel-185"). |
'EEG10-10'
|
progress_callback
|
Callable | None
|
Optional callback(message, level) for GUI updates. |
None
|
termination_flag
|
Callable[[], bool] | None
|
Optional callable returning True when cancelled. |
None
|
Source code in tit/opt/leadfield.py
generate ¶
generate(output_dir: str | Path | None = None, tissues: list[int] | None = None, cleanup: bool = True) -> Path
Generate a leadfield matrix via SimNIBS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_dir
|
str | Path | None
|
Output directory (default: pm.leadfields(subject_id)). |
None
|
tissues
|
list[int] | None
|
Tissue tags [1=WM, 2=GM]. Default: [1, 2]. |
None
|
cleanup
|
bool
|
Remove stale SimNIBS artefacts before running. |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the generated HDF5 leadfield file. |
Raises:
| Type | Description |
|---|---|
InterruptedError
|
If cancelled via termination_flag. |
Source code in tit/opt/leadfield.py
list_leadfields ¶
List available leadfield HDF5 files for a subject.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject_id
|
str | None
|
Subject ID (defaults to self.subject_id). |
None
|
Returns:
| Type | Description |
|---|---|
list[tuple[str, str, float]]
|
Sorted list of (net_name, hdf5_path, size_gb) tuples. |
Source code in tit/opt/leadfield.py
get_electrode_names ¶
Extract electrode labels from an EEG cap via SimNIBS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cap_name
|
str | None
|
EEG cap name (without .csv). Defaults to self.electrode_cap. |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
Sorted list of electrode label strings. |