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}
MESH([Head Mesh]) --> FLEX
MESH --> EX
NET([EEG Net]) --> FLEX
NET --> EX
OPT -->|continuous| FLEX[Flex-Search]
OPT -->|discrete| EX[Ex-Search]
FLEX --> RESULT([Optimal Montage])
EX --> RESULT
style ROI fill:#1a3a5c,stroke:#48a,color:#fff
style MESH fill:#1a3a5c,stroke:#48a,color:#fff
style NET fill:#1a3a5c,stroke:#48a,color:#fff
style OPT fill:#5c4a1a,stroke:#a84,color:#fff
style FLEX fill:#2d5a27,stroke:#4a8,color:#fff
style EX fill:#2d5a27,stroke:#4a8,color:#fff
style RESULT fill:#1a5c4a,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",
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",
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",
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, 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, min_electrode_distance: float = 5.0, detailed_results: bool = False, visualize_valid_skin_region: bool = False, skin_visualization_net: str | None = None)
Full configuration for flex-search optimization.
Wraps all parameters needed to drive a SimNIBS
TesFlexOptimization run, including subject, ROI definition,
electrode geometry, DE hyperparameters, and output control.
Attributes¶
subject_id : str
Subject identifier matching the m2m directory name.
goal : OptGoal
Optimization objective ("mean", "max", or "focality").
postproc : FieldPostproc
Field post-processing method ("max_TI", "dir_TI_normal",
or "dir_TI_tangential").
current_mA : float
Total injected current in milliamps.
electrode : ElectrodeConfig
Electrode geometry configuration.
roi : SphericalROI or AtlasROI or SubcorticalROI
Target region of interest.
anisotropy_type : str
Conductivity tensor type ("scalar" or "vn").
aniso_maxratio : float
Maximum anisotropy eigenvalue ratio.
aniso_maxcond : float
Maximum anisotropic conductivity (S/m).
non_roi_method : NonROIMethod or None
How to define the non-ROI region for focality optimization.
None when goal is not focality.
non_roi : SphericalROI or AtlasROI or SubcorticalROI or None
Explicit non-ROI region when non_roi_method is "specific".
thresholds : str or None
Comma-separated focality threshold values (e.g. "0.1,0.2").
eeg_net : str or None
EEG net filename (e.g. "GSN-HydroCel-185.csv") for
electrode-name mapping. None to use raw electrode indices.
enable_mapping : bool
If True, map optimal indices to named EEG positions.
disable_mapping_simulation : bool
If True, skip the final named-electrode simulation after mapping.
output_folder : str or None
Override for the output directory path. Defaults to an
auto-generated timestamped folder.
run_final_electrode_simulation : bool
If True, run a full SimNIBS simulation with the winning
electrode configuration.
n_multistart : int
Number of independent DE restarts. Higher values reduce
sensitivity to local optima.
max_iterations : int or None
Maximum DE generations per restart. None for solver default.
population_size : int or None
DE population size. None for solver default.
tolerance : float or None
Convergence tolerance for DE. None for solver default.
mutation : str or None
DE mutation strategy string. None for solver default.
recombination : float or None
DE crossover probability. None for solver default.
cpus : int or None
Number of parallel workers. None for auto-detect.
min_electrode_distance : float
Minimum geodesic distance (mm) between any two electrodes.
detailed_results : bool
If True, save per-restart detailed output.
visualize_valid_skin_region : bool
If True, save a mesh showing the valid electrode placement region.
skin_visualization_net : str or None
EEG net to overlay on the skin visualization.
Raises¶
ValueError
If goal is "focality" with non_roi_method "specific"
but non_roi is None, or if thresholds contains
non-numeric values.
See Also¶
FlexResult : Result container returned by :func:~tit.opt.flex.flex.run_flex_search.
tit.opt.flex.flex.run_flex_search : Consumes this config.
OptGoal ¶
FieldPostproc ¶
NonROIMethod ¶
SphericalROI
dataclass
¶
SphericalROI(x: float, y: float, z: float, radius: float = 10.0, use_mni: bool = False, volumetric: bool = False, tissues: str = 'GM')
Spherical region of interest defined by center and radius.
By default the sphere is evaluated on the cortical surface
(volumetric=False). Set volumetric=True to evaluate on
volume tetrahedra instead -- useful for deep/subcortical targets
like the amygdala or hippocampus where surface-only evaluation
would capture overlying cortex rather than the target structure.
When volumetric=True, the tissues field controls which
tissue compartments are included (same semantics as
:class:SubcorticalROI.tissues).
Attributes¶
x : float
Center x-coordinate (mm).
y : float
Center y-coordinate (mm).
z : float
Center z-coordinate (mm).
radius : float
Sphere radius in mm.
use_mni : bool
If True, coordinates are in MNI space and will be transformed
to subject space automatically.
volumetric : bool
If True, evaluate on volume tetrahedra instead of the cortical
surface.
tissues : str
Tissue compartments to include when volumetric is True.
One of "GM", "WM", or "both".
AtlasROI
dataclass
¶
Cortical surface ROI from a FreeSurfer annotation atlas.
Attributes¶
atlas_path : str
Path to the FreeSurfer .annot annotation file.
label : int
Integer label index within the annotation atlas.
hemisphere : str
Hemisphere to use ("lh" or "rh").
SubcorticalROI
dataclass
¶
Subcortical volume ROI from a volumetric atlas.
Attributes¶
atlas_path : str
Path to the volumetric atlas NIfTI file.
label : int
Integer label index within the volumetric atlas.
tissues : str
Tissue compartments to include. One of "GM", "WM",
or "both".
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.
Attributes¶
shape : str
Electrode shape ("ellipse" or "rect").
dimensions : list of float
Electrode dimensions in mm ([width, height]).
gel_thickness : float
Conductive gel thickness in mm.
tit.opt.flex.flex.run_flex_search ¶
run_flex_search(config: FlexConfig) -> FlexResult
Run differential-evolution electrode placement optimization.
Uses scipy.optimize.differential_evolution (via SimNIBS
TesFlexOptimization) to find electrode positions that maximize
field strength, peak intensity, or focality in a target ROI.
Multiple independent restarts (controlled by
config.n_multistart) are executed sequentially; the best run's
output is promoted to the base output folder.
Parameters¶
config : FlexConfig Fully specified optimization configuration including subject, ROI definition, electrode geometry, and DE hyperparameters.
Returns¶
FlexResult Optimization outcomes including best montage, objective value, and convergence diagnostics.
See Also¶
FlexConfig : Configuration dataclass for flex-search. FlexResult : Result container with per-restart function values. tit.opt.ex.ex.run_ex_search : Alternative exhaustive grid search.
Source code in tit/opt/flex/flex.py
tit.opt.config.FlexConfig.SphericalROI
dataclass
¶
SphericalROI(x: float, y: float, z: float, radius: float = 10.0, use_mni: bool = False, volumetric: bool = False, tissues: str = 'GM')
Spherical region of interest defined by center and radius.
By default the sphere is evaluated on the cortical surface
(volumetric=False). Set volumetric=True to evaluate on
volume tetrahedra instead -- useful for deep/subcortical targets
like the amygdala or hippocampus where surface-only evaluation
would capture overlying cortex rather than the target structure.
When volumetric=True, the tissues field controls which
tissue compartments are included (same semantics as
:class:SubcorticalROI.tissues).
Attributes¶
x : float
Center x-coordinate (mm).
y : float
Center y-coordinate (mm).
z : float
Center z-coordinate (mm).
radius : float
Sphere radius in mm.
use_mni : bool
If True, coordinates are in MNI space and will be transformed
to subject space automatically.
volumetric : bool
If True, evaluate on volume tetrahedra instead of the cortical
surface.
tissues : str
Tissue compartments to include when volumetric is True.
One of "GM", "WM", or "both".
tit.opt.config.FlexConfig.AtlasROI
dataclass
¶
Cortical surface ROI from a FreeSurfer annotation atlas.
Attributes¶
atlas_path : str
Path to the FreeSurfer .annot annotation file.
label : int
Integer label index within the annotation atlas.
hemisphere : str
Hemisphere to use ("lh" or "rh").
tit.opt.config.FlexConfig.SubcorticalROI
dataclass
¶
Subcortical volume ROI from a volumetric atlas.
Attributes¶
atlas_path : str
Path to the volumetric atlas NIfTI file.
label : int
Integer label index within the volumetric atlas.
tissues : str
Tissue compartments to include. One of "GM", "WM",
or "both".
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.
Attributes¶
shape : str
Electrode shape ("ellipse" or "rect").
dimensions : list of float
Electrode dimensions in mm ([width, height]).
gel_thickness : float
Conductive gel thickness in mm.
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.
Attributes¶
success : bool True if the optimization completed without error. output_folder : str Absolute path to the output directory containing manifests, logs, and optional simulation results. function_values : list of float Objective function value for each multistart run. best_value : float Best (highest) objective value across all restarts. best_run_index : int Zero-based index of the restart that produced the best result.
See Also¶
FlexConfig : Configuration consumed by :func:~tit.opt.flex.flex.run_flex_search.
tit.opt.flex.flex.run_flex_search : Returns this result.
Exhaustive Search¶
tit.opt.config.ExConfig
dataclass
¶
ExConfig(subject_id: 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.
Exhaustive search evaluates every valid electrode combination from a user-defined pool or bucket set, sweeping current amplitudes at discrete steps.
Attributes¶
subject_id : str
Subject identifier matching the m2m directory name.
leadfield_hdf : str
Path to the precomputed leadfield HDF5 file.
roi_name : str
ROI CSV filename (e.g. "target.csv"). The ".csv" suffix
is appended automatically if missing.
electrodes : BucketElectrodes or PoolElectrodes
Electrode specification, either a single shared pool
(:class:PoolElectrodes) or separate per-channel buckets
(:class:BucketElectrodes). A plain dict is auto-converted
in __post_init__.
total_current : float
Total injected current in mA, split across channels.
current_step : float
Current amplitude step size in mA for the sweep.
channel_limit : float or None
Maximum current per channel in mA. None for no per-channel
limit.
roi_radius : float
Spherical ROI radius in mm for the target region.
run_name : str or None
Optional name for this run. Defaults to a datetime stamp.
Raises¶
ValueError If current_step, total_current, or channel_limit are non-positive.
See Also¶
ExResult : Result container returned by :func:~tit.opt.ex.ex.run_ex_search.
tit.opt.ex.ex.run_ex_search : Consumes this config.
BucketElectrodes
dataclass
¶
Separate electrode lists for each bipolar channel position.
Attributes¶
e1_plus : list of str Candidate electrodes for channel 1 anode. e1_minus : list of str Candidate electrodes for channel 1 cathode. e2_plus : list of str Candidate electrodes for channel 2 anode. e2_minus : list of str Candidate electrodes for channel 2 cathode.
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.
Attributes¶
electrodes : list of str List of electrode names available for any channel position.
tit.opt.config.ExConfig.BucketElectrodes
dataclass
¶
Separate electrode lists for each bipolar channel position.
Attributes¶
e1_plus : list of str Candidate electrodes for channel 1 anode. e1_minus : list of str Candidate electrodes for channel 1 cathode. e2_plus : list of str Candidate electrodes for channel 2 anode. e2_minus : list of str Candidate electrodes for channel 2 cathode.
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.
Attributes¶
success : bool
True if the search completed without error.
output_dir : str
Absolute path to the output directory.
n_combinations : int
Total number of electrode/current combinations evaluated.
results_csv : str or None
Path to the CSV file containing ranked results. None if the
run failed before writing results.
config_json : str or None
Path to the saved configuration JSON. None if the run failed
before writing config.
See Also¶
ExConfig : Configuration consumed by :func:~tit.opt.ex.ex.run_ex_search.
tit.opt.ex.ex.run_ex_search : Returns this result.
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.
Wraps SimNIBS TDCSLEADFIELD to produce HDF5 leadfield files
that the exhaustive-search and flex-search pipelines consume.
Parameters¶
subject_id : str
Subject identifier (e.g. "101").
electrode_cap : str
EEG cap name without .csv (e.g. "GSN-HydroCel-185").
progress_callback : callable or None
Optional callback(message, level) for GUI progress updates.
termination_flag : callable or None
Optional callable returning True when the user cancels.
See Also¶
tit.opt.ex.engine.ExSearchEngine : Consumes the generated leadfield.
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¶
output_dir : str or Path or None
Output directory. Defaults to
pm.leadfields(subject_id).
tissues : list of int or None
Tissue tags (1 = WM, 2 = GM). Default: [1, 2].
cleanup : bool
Remove stale SimNIBS artefacts before running.
Returns¶
Path Path to the generated HDF5 leadfield file.
Raises¶
InterruptedError If cancelled via termination_flag.
Source code in tit/opt/leadfield.py
list_leadfields ¶
List available leadfield HDF5 files for a subject.
Parameters¶
subject_id : str or None
Subject ID. Defaults to self.subject_id.
Returns¶
list of 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¶
cap_name : str or None
EEG cap name (without .csv). Defaults to
self.electrode_cap.
Returns¶
list of str Sorted list of electrode label strings.