opt
tit.opt ¶
TI-Toolbox optimization package.
Provides two electrode-placement optimization strategies for temporal interference (TI) brain stimulation:
- Flex-search -- differential-evolution (DE) optimization via SimNIBS
TesFlexOptimization. Finds continuous electrode positions on the scalp that maximize field strength, peak intensity, or focality in a user-defined ROI. - Exhaustive search -- brute-force grid evaluation over a discrete electrode pool, sweeping current amplitudes at fixed step sizes.
- Multipolar exhaustive search -- the same brute-force evaluation extended to four bipolar electrode pairs (eight electrodes), scored with the N>2 mTI envelope.
Public API¶
FlexConfig
Configuration dataclass for flex-search optimization.
FlexResult
Result container returned by :func:run_flex_search.
ExConfig
Configuration dataclass for exhaustive search.
ExResult
Result container returned by :func:run_ex_search.
MExConfig
Configuration dataclass for multipolar exhaustive search.
MExResult
Result container returned by :func:run_m_ex_search.
run_flex_search
Run differential-evolution electrode placement optimization.
run_ex_search
Run exhaustive grid search over electrode combinations.
run_m_ex_search
Run multipolar exhaustive grid search over four electrode pairs.
run_adaptive_focality
Flex-search mean calibration, then focality with thresholds from it.
run_pareto_sweep
Flex-search mean calibration, then a grid of focality thresholds.
See Also¶
tit.opt.flex : Flex-search subpackage with builder, manifest, and pareto utilities. tit.opt.ex : Exhaustive-search subpackage with engine and result handling. tit.opt.mex : Multipolar exhaustive-search subpackage. tit.opt.leadfield : Leadfield matrix generation via SimNIBS.
Examples¶
from tit.opt import FlexConfig, run_flex_search cfg = FlexConfig( ... subject_id="ernie", goal="mean", postproc="max_TI", current_mA=1.0, ... electrode=FlexConfig.ElectrodeConfig(), ... roi=FlexConfig.SphericalROI(x=-35.0, y=5.0, z=5.0, radius=10.0, use_mni=True), ... ) res = run_flex_search(cfg) # doctest: +SKIP res.success, res.best_value # doctest: +SKIP (True, 0.31)
Notes¶
The runners (run_flex_search, the flex drivers, run_ex_search and
run_m_ex_search) are loaded lazily on first attribute access so that
importing tit.opt for its config classes does not import SimNIBS.
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, intensity_weight: float = 0.0, optimize_current_ratio: bool = False, ratio_total_mA: float | None = None, ratio_levels: int = 21, eeg_net: str | None = None, enable_mapping: bool = False, disable_mapping_simulation: bool = False, observe_background: 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 = True, skin_visualization_net: str | None = None, skin_region_margin_mm: float = 0.0, avoid_landmark_regions: bool = True, mode: Mode = FLEX, adaptive: AdaptiveFocalityConfig | None = None, pareto: ParetoSweepConfig | 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", "focality", or
"focality_tf").
postproc : FieldPostproc
Field post-processing method ("max_TI", "dir_TI_normal",
or "dir_TI_tangential").
current_mA : float
Current per channel in mA -- each of the two channels
injects +current_mA / -current_mA (a 1:1 split), so the
total injected current is 2 * current_mA.
electrode : ElectrodeConfig
Electrode geometry configuration.
roi : SphericalROI or AtlasROI or SubcorticalROI
Target region of interest.
anisotropy_type : str
Conductivity tensor type ("scalar" or "vn"). Default
"scalar".
aniso_maxratio : float
Maximum anisotropy eigenvalue ratio. Default 10.0.
aniso_maxcond : float
Maximum anisotropic conductivity (S/m). Default 2.0.
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
Focality thresholds in V/m for the ROC-based "focality" goal: one
value (non-ROI max = ROI min) or two, "<non-ROI max>,<ROI min>"
(e.g. "0.1,0.2"), each finite and greater than 0, the first not
above the second (SimNIBS measures.ROC's safety gap). A number or
a list of numbers is stored as that text (0.2 -> "0.2").
None and the placeholders "", "dynamic", "auto" mean
unset and are stored as None. A single "focality" run
requires them -- SimNIBS has no default -- while mode="flex_adaptive" /
"flex_pareto" (or a config carrying adaptive / pareto
settings, whose mode the job kind supplies) derive them per step.
intensity_weight : float
Weight w in [0, 1] trading ROI intensity against focality
for the "focality_tf" goal. 0.0 gives the balanced form,
1.0 weights raw ROI intensity most heavily. Ignored by the
other goals.
optimize_current_ratio : bool
If True, jointly search the electrode placement and the
current split between the two channels instead of fixing it at
1:1. Applies to any goal. Scoring then happens in a Python
callable rather than in SimNIBS, which has two consequences:
goal="focality" in mode="flex" requires explicit
thresholds (the adaptive and multi-threshold modes derive them
per step), and detailed_results cannot be used.
ratio_total_mA : float or None
Total current (mA) shared by the two channels during the ratio
search. None uses 2 * current_mA, i.e. the 1:1 split is
contained in the search range. When set explicitly it must be
greater than zero.
ratio_levels : int
Number of discrete current splits evaluated per candidate
placement. Must be at least 2 when optimize_current_ratio is
True.
eeg_net : str or None
EEG net name or filename (e.g. "GSN-HydroCel-185" or
"GSN-HydroCel-185.csv") for electrode-name mapping.
None (default) to use raw electrode indices.
enable_mapping : bool
If True, map optimal indices to named EEG positions. Default
False.
disable_mapping_simulation : bool
If True, skip the final named-electrode simulation after mapping.
Default False.
observe_background : bool
If True, also record observation-only background (non-ROI) field
metrics for every candidate, independent of the goal. Like the
callable goals it is incompatible with detailed_results.
Default False.
output_folder : str or None
Override for the output directory path; a relative name is a
folder under the subject's flex-search/. 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 (default 1). Higher values
reduce sensitivity to local optima; this is the only thing
cpus parallelises.
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 for the multi-start restarts (the DE
search itself is single-process). None uses the global CPU
limit (:func:tit.cpu.cpu_limit); a larger value is clamped to it.
min_electrode_distance : float
Minimum distance (mm) SimNIBS keeps between any two electrodes,
greater than 0. Default 5.0. SimNIBS measures it as the
straight-line distance between the skin-mesh nodes under each
electrode (check_electrode_distance), not edge to edge: nodes
sit inside the footprint, so the physical gap between two discs
(centre distance minus both radii) can be several millimetres
smaller -- 1.75 mm for the 5 mm default between 8 mm discs on a
real head (audit 2026-10-22).
detailed_results : bool
If True, save per-restart detailed output. Incompatible with any
configuration whose goal is a Python callable -- "focality_tf"
or optimize_current_ratio -- because SimNIBS writes opt.goal
into the detailed-results HDF5 file and h5py cannot serialise a
function. The combination is rejected at config time rather than
after the (potentially hours-long) optimization has finished.
visualize_valid_skin_region : bool
Accepted for compatibility and ignored: every flex-search writes
valid_skin_region.png (the report shows it), so
:func:tit.opt.flex.builder.build_optimization always turns the
picture on.
skin_visualization_net : str or None
EEG net to overlay on the skin visualization.
skin_region_margin_mm : float
Signed margin in millimeters applied to the SimNIBS valid-skin
region. Positive values expand the region, negative values
constrict it. The default 0.0 preserves SimNIBS behavior.
avoid_landmark_regions : bool
If True, positive skin-region margins keep fiducial-derived
ear and orbital exclusion regions invalid.
mode : Mode
Which driver runs this config: a single run ("flex", the
default), :func:tit.opt.flex.drivers.run_adaptive_focality
("flex_adaptive"), or :func:tit.opt.flex.drivers.run_pareto_sweep
("flex_pareto"). Set by the job kind
(tit.jobs.kinds.MODULE_FOR_KIND); tit.opt.flex.__main__
dispatches on it.
adaptive : AdaptiveFocalityConfig or None
Threshold percentages for mode="flex_adaptive". Defaulted when
left None under that mode.
pareto : ParetoSweepConfig or None
Threshold percentage grid for mode="flex_pareto". Defaulted
when left None under that mode.
Raises¶
ValueError
If goal is "focality" with non_roi_method "specific"
but non_roi is None or equal to roi, if thresholds is
malformed (see thresholds) or missing for a single "focality"
run, if current_mA, min_electrode_distance, an electrode size or
a sphere radius is not a positive finite number (or a sphere centre
is not finite), if n_multistart or cpus is below 1, if an
"ellipse" electrode has unequal dimensions, if a subcortical
atlas_space is neither "subject" nor "mni", if
output_folder's last component breaks :data:tit.paths.NAME_RE,
if intensity_weight falls outside [0, 1], if
ratio_levels is below 2 while optimize_current_ratio is True,
if ratio_total_mA is set but not positive, if
optimize_current_ratio is combined with goal="focality"
in mode="flex" without explicit thresholds, if
detailed_results is combined with a callable-goal configuration
(goal="focality_tf", optimize_current_ratio or
observe_background), or if mode is
"flex_adaptive"/"flex_pareto" while goal is not
"focality".
Examples¶
Enum-valued fields accept their string values:
from tit.opt import FlexConfig cfg = FlexConfig( ... subject_id="ernie", ... goal="mean", # "mean" | "max" | "focality" | "focality_tf" ... 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=-35.0, y=5.0, z=5.0, radius=10.0, use_mni=True), ... n_multistart=3, ... ) cfg.goal is FlexConfig.OptGoal.MEAN, cfg.is_focality (True, False)
A focality goal defaults its non-ROI region to everything outside the ROI:
foc = FlexConfig( ... subject_id="ernie", goal="focality_tf", postproc="max_TI", current_mA=1.0, ... electrode=FlexConfig.ElectrodeConfig(), ... roi=FlexConfig.AtlasROI(atlas_path="lh.aparc.annot", label=24, hemisphere="lh"), ... ) foc.non_roi_method.value 'everything_else'
Then run_flex_search(cfg) (needs SimNIBS and the subject's m2m
directory).
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.
is_focality
property
¶
is_focality: bool
True for any focality goal (ROC-based or threshold-free).
Focality goals share the same ROI/non-ROI setup (a target ROI plus a
non-ROI region), so callers use this to gate non-ROI construction and
reporting. Threshold-specific logic keeps comparing against
OptGoal.FOCALITY directly, since only the ROC goal uses thresholds.
OptGoal ¶
Bases: StrEnum
Optimization goal.
Attributes¶
MEAN : str
Maximize mean field intensity in the ROI.
MAX : str
Maximize the 99.9th percentile field intensity in the ROI.
FOCALITY : str
Maximize ROI-to-non-ROI focality via SimNIBS's threshold-based
ROC measure (measures.ROC).
FOCALITY_TF : str
Maximize a threshold-free focality contrast,
mean(E_ROI) ** (1 + w) / mean(E_nonROI). Because it needs no
thresholds it avoids the threshold-selection failure mode of the
ROC goal, whose landscape flattens when the requested ROI and
non-ROI thresholds are jointly infeasible (as happens at deep
targets). The weight w is
:attr:FlexConfig.intensity_weight.
FieldPostproc ¶
Bases: StrEnum
Field post-processing method applied to the TI envelope.
Attributes¶
MAX_TI : str Maximum TI envelope over all directions. DIR_TI_NORMAL : str TI envelope along the cortical surface normal. DIR_TI_TANGENTIAL : str Maximum TI envelope over the directions tangential to the cortical surface (within its plane).
NonROIMethod ¶
Mode ¶
Bases: StrEnum
Which flex-search driver runs this config (tit.jobs.kinds.MODULE_FOR_KIND
maps flex/flex_adaptive/flex_pareto job kinds to this same module;
tit.opt.flex.__main__ dispatches on this field to pick the driver).
Attributes¶
FLEX : str
A single :func:~tit.opt.flex.flex.run_flex_search run (any goal).
FLEX_ADAPTIVE : str
Two-step adaptive focality: a "mean" run to find the achievable ROI
intensity, then a "focality" run with thresholds derived from
:attr:FlexConfig.adaptive. Requires goal="focality".
FLEX_PARETO : str
A "mean" calibration run followed by a grid of "focality" runs
over :attr:FlexConfig.pareto's threshold percentages. Requires
goal="focality".
AdaptiveFocalityConfig
dataclass
¶
Thresholds for :func:tit.opt.flex.drivers.run_adaptive_focality.
Both percentages are applied to the achievable mean ROI intensity found by
the driver's own step-1 "mean" optimization run -- never to a value the
caller supplies directly, since that intensity is subject- and ROI-specific
and cannot be known ahead of time.
Attributes¶
roi_percentage : float ROI focality threshold, as a percentage (0-100 exclusive) of the achievable mean ROI intensity. nonroi_percentage : float Non-ROI focality threshold, as a percentage (0-100 exclusive) of the same achievable intensity. Must be strictly less than roi_percentage.
Raises¶
ValueError
If either percentage is outside (0, 100), or if
nonroi_percentage is not strictly less than roi_percentage.
ParetoSweepConfig
dataclass
¶
ParetoSweepConfig(roi_pcts: list[float] = (lambda: [80.0])(), nonroi_pcts: list[float] = (lambda: [20.0, 30.0, 40.0])())
Threshold grid for :func:tit.opt.flex.drivers.run_pareto_sweep.
The driver runs one "focality" optimization per (roi_pct, nonroi_pct)
combination in the Cartesian product of roi_pcts and *nonroi_pcts| --
len(roi_pcts) * len(nonroi_pcts) runs total, in addition to the single
step-1 "mean" calibration run. See
:func:tit.opt.flex.pareto.compute_sweep_grid.
Attributes¶
roi_pcts : list of float
ROI threshold percentages to sweep (each in (0, 100)).
nonroi_pcts : list of float
Non-ROI threshold percentages to sweep (each in (0, 100)).
Raises¶
ValueError
If either list is empty, if any value falls outside (0, 100),
or if any (roi_pct, nonroi_pct) combination has
nonroi_pct >= roi_pct.
SphericalROI
dataclass
¶
SphericalROI(x: float | list[float], y: float | list[float], z: float | list[float], radius: float | list[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).
Each of x, y, z, radius accepts either a single value (one sphere) or a list of values (a union of several spheres evaluated as one combined target). The coordinate lists must be non-empty and of equal length; radius may be a scalar (shared by every sphere) or a list matching the number of centers.
Attributes¶
x : float or list of float
Center x-coordinate(s) (mm).
y : float or list of float
Center y-coordinate(s) (mm).
z : float or list of float
Center z-coordinate(s) (mm).
radius : float or list of float
Sphere radius/radii in mm. A scalar is shared by all spheres.
use_mni : bool
If True, coordinates are in MNI space and SimNIBS will transform
them to subject space during ROI setup.
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".
Raises¶
ValueError
If x/y/z are empty or unequal length, or radius is a list
whose length neither equals 1 nor the number of centers, or
tissues is not "GM", "WM" or "both".
AtlasROI
dataclass
¶
Cortical surface ROI from a FreeSurfer annotation atlas.
Each of atlas_path, label, hemisphere accepts either a single
value (one region) or a list (a union of several regions evaluated as
one combined target). Because .annot files are per-hemisphere,
carrying a per-region hemisphere (and matching atlas_path) allows a
target that spans both hemispheres, or even different atlases.
Scalars broadcast to the number of labels; lists must match its length.
Attributes¶
atlas_path : str or list of str
Path(s) to the FreeSurfer .annot annotation file(s).
label : int or list of int
Integer label index/indices within the annotation atlas.
hemisphere : str or list of str
Hemisphere(s) to use ("lh" or "rh"), one per label.
Raises¶
ValueError
If label is empty, or atlas_path/hemisphere is a list whose
length neither equals 1 nor the number of labels, or a
hemisphere is not "lh" or "rh".
SubcorticalROI
dataclass
¶
SubcorticalROI(atlas_path: str | list[str], label: int | list[int] | None, tissues: str = 'GM', atlas_space: Literal['subject', 'mni'] = 'subject')
Subcortical volume ROI from a volumetric atlas.
label accepts either a single value (one region) or a list (a union
of several regions -- e.g. both hippocampi from one aseg atlas --
evaluated as one combined target). atlas_path may be a scalar
(shared by every label) or a list matching the number of labels; a
single shared tissues and atlas_space apply to the whole union.
Attributes¶
atlas_path : str or list of str
Path(s) to the volumetric atlas NIfTI file(s).
label : int or list of int
Integer label index/indices, or None to select all positive mask voxels.
tissues : str
Tissue compartments to include. One of "GM", "WM",
or "both".
atlas_space : str
Space of the atlas NIfTI. One of "subject" or "mni".
MNI-space masks are transformed by SimNIBS during ROI setup.
Raises¶
ValueError
If label is empty, or atlas_path is a list whose length
neither equals 1 nor the number of labels, or tissues is not
"GM", "WM" or "both".
ElectrodeConfig
dataclass
¶
ElectrodeConfig(shape: str = 'ellipse', dimensions: list[float] = (lambda: [8.0, 8.0])(), gel_thickness: float = 1.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"). Default
"ellipse". Flex-search supports circular electrodes only,
so an "ellipse" must have equal dimensions.
dimensions : list of float
Electrode dimensions in mm ([width, height]). Default
[8.0, 8.0].
gel_thickness : float
Conductive gel (saline) thickness in mm. Default 1.0.
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 objective value across all restarts: the lowest, because
SimNIBS minimises. A maximised goal is minimised as its negation,
so for "mean" this is -mean field in the ROI (V/m).
best_run_index : int
Zero-based index of the restart that produced the best result.
Examples¶
res = run_flex_search(cfg) # doctest: +SKIP res.success, res.best_value, res.output_folder # doctest: +SKIP (True, -0.31, '.../flex-search/ernie_...')
See Also¶
FlexConfig : Configuration consumed by :func:~tit.opt.flex.flex.run_flex_search.
tit.opt.flex.flex.run_flex_search : Returns this result.
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, roi_names: list[str] | None = None, roi_atlas: list[AtlasROI] | None = None, roi_coordinate_space: Literal['subject', 'mni'] = 'subject', run_name: str | None = None, n_jobs: int = -1, symmetric_bucket: bool = False, symmetry_eeg_csv: str | None = None, symmetry_pairing: str = 'within_pairs')
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
Filename of the precomputed leadfield HDF5 (e.g.
"ernie_leadfield_EEG10-10_UI_Jurak_2007.hdf5"), resolved under
the subject's leadfields/ directory
(:meth:tit.paths.PathManager.leadfields); an absolute path is
also accepted.
roi_name : str
ROI CSV filename (e.g. "target.csv") in the subject's ROIs/
directory (:meth:tit.paths.PathManager.rois). The ".csv"
suffix is appended automatically if missing. Used as the
metric-key prefix and (with the net name) the output-directory
label.
roi_names : list of str or None
Optional list of ROI CSV filenames to union into a single
target. When provided (combined mode), the spherical masks of
every listed ROI are OR-folded into one region. None
(default) keeps single-ROI behavior driven by roi_name. An
explicit empty list means "no spherical centers at all" -- useful
for a purely atlas-driven ROI (roi_atlas only). Each entry gets
the ".csv" suffix appended if missing.
roi_atlas : list of AtlasROI or None
Volumetric atlas or mask ROI(s) to union with the spherical
centers from roi_name/roi_names. None (default) keeps
the existing spherical-only behavior.
roi_coordinate_space : str
Space of the roi_name/roi_names CSV centers -- "subject"
(default) or "mni". MNI centers are transformed to subject
space with simnibs.mni2subject_coords before the search runs.
Does not affect roi_atlas, which declares its own atlas_space.
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 the two channels.
Default 2.0.
current_step : float
Current amplitude step size in mA for the sweep (used only with
a channel_limit). Default 0.5.
channel_limit : float or None
Maximum current per channel in mA. None (default) runs the
single balanced split, total_current / 2 on each channel;
set a limit above total_current / 2 to sweep splits in
current_step increments.
roi_radius : float
Spherical ROI radius in mm for the target region. Default 3.0.
run_name : str or None
Optional name for this run. Defaults to a datetime stamp. The
run name is the output directory, so a repeated name overwrites
the earlier run in place.
n_jobs : int
Worker processes evaluating candidates in parallel. -1
(default) uses the global CPU limit (Settings; 70 % of the
container's cores by default, see :func:tit.cpu.cpu_limit);
a larger explicit value is clamped to that limit; 1
evaluates in-process. Results and CSV ordering do not depend
on it.
symmetric_bucket : bool
When True in bucket mode, evaluate only left/right mirrored
montages (see :func:tit.opt.ex.buckets.build_electrode_mirror_map).
symmetry_eeg_csv : str or None
EEG-position CSV used to derive mirrored electrode pairs. If
unset, it is inferred from the leadfield's net name.
symmetry_pairing : str
Symmetry interpretation when symmetric_bucket is True.
"within_pairs": each channel's minus electrode is the mirror of
its plus electrode (e.g. F7-F8). "cross_pairs": channel 2 is the
mirror image of channel 1 (e2+ = mirror(e1+), e2- = mirror(e1-)).
Raises¶
ValueError
If current_step, total_current, channel_limit or
roi_radius is not a finite number above 0, if symmetric_bucket is set with pool electrodes,
if symmetry_pairing is not "within_pairs"/"cross_pairs",
or if roi_coordinate_space is not "subject" or "mni".
Examples¶
from tit.opt import ExConfig cfg = ExConfig( ... subject_id="ernie", ... leadfield_hdf="ernie_leadfield_EEG10-10_UI_Jurak_2007.hdf5", ... roi_name="L-Insula", # ".csv" is appended ... electrodes=ExConfig.PoolElectrodes( ... electrodes=["Fp1", "Fp2", "C3", "C4", "Cz", "Pz", "T7", "T8"]), ... total_current=2.0, current_step=0.5, channel_limit=1.2, ... roi_coordinate_space="mni", ... ) cfg.roi_name 'L-Insula.csv'
Per-channel buckets instead of one pool:
ExConfig.BucketElectrodes(e1_plus=["F7"], e1_minus=["F8"], ... e2_plus=["P7"], e2_minus=["P8"]).e2_minus ['P8']
Then run_ex_search(cfg) (needs the leadfield HDF5 under the
subject's leadfield folder).
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.
AtlasROI
dataclass
¶
AtlasROI(atlas_path: str, label: int | None = None, atlas_space: Literal['subject', 'mni'] = 'subject')
Volumetric atlas or mask ROI, unioned with the spherical center(s).
Attributes¶
atlas_path : str
Path to a volumetric atlas or mask file -- NIfTI (.nii,
.nii.gz) or FreeSurfer (.mgz), e.g. one discovered by
:class:tit.atlas.voxel.VoxelAtlasManager.
label : int or None
Integer label to select within the atlas (elements are
included where the voxel value equals label). None
treats the whole file as a binary mask (voxel value > 0).
atlas_space : str
Space of the atlas file, "subject" (default) or "mni".
MNI masks are warped to subject space before the search.
Raises¶
ValueError
If atlas_space is not "subject" or "mni".
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.
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.
MExConfig
dataclass
¶
MExConfig(subject_id: str, leadfield_hdf: str, roi_name: str, electrodes: BucketElectrodes | PoolElectrodes, current_mA: float = 2.0, roi_radius: float = 3.0, roi_names: list[str] | None = None, roi_atlas: list[AtlasROI] | None = None, roi_coordinate_space: Literal['subject', 'mni'] = 'subject', run_name: str | None = None, n_jobs: int = -1, symmetric_bucket: bool = False, symmetry_eeg_csv: str | None = None, symmetry_pairing: str = 'within_pairs')
Full configuration for multipolar (four-channel) exhaustive search.
Evaluates every valid combination of four channels (eight electrodes)
from a user-defined pool or bucket set, at one fixed current per channel,
and scores each candidate with the joint K-pair envelope maximum
(:func:tit.calc.envelope_max).
Attributes¶
subject_id : str
Subject identifier matching the m2m directory name.
leadfield_hdf : str
Filename of the precomputed leadfield HDF5, resolved under the
subject's leadfields/ directory (an absolute path also works).
roi_name : str
ROI CSV filename (e.g. "target.csv") in the subject's ROIs/
directory. 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__.
current_mA : float
Current in mA delivered by each of the four channels. Default 2.0.
roi_radius : float
Spherical ROI radius in mm for the target region. Default 3.0.
roi_names : list of str or None
Optional list of ROI CSV filenames to union into a single
target. None (default) keeps single-ROI behavior driven by
roi_name. An explicit empty list means "no spherical centers at
all" -- required for a purely atlas-driven ROI (roi_atlas only),
in which case roi_name is only a naming label and no CSV is read.
Each entry gets the ".csv" suffix appended if missing.
roi_atlas : list of AtlasROI or None
Volumetric atlas or mask ROI(s) to union with the spherical center
from roi_name. None (default) keeps the existing
spherical-only behavior.
roi_coordinate_space : str
Space of the roi_name CSV center -- "subject" (default) or
"mni". An MNI center is transformed to subject space with
simnibs.mni2subject_coords before the search runs. Does not
affect roi_atlas, which declares its own atlas_space.
run_name : str or None
Optional name for this run. Defaults to a datetime stamp.
n_jobs : int
Worker processes evaluating candidates in parallel. -1
(default) uses the global CPU limit (Settings; 70 % of the
container's cores by default, see :func:tit.cpu.cpu_limit);
a larger explicit value is clamped to that limit; 1
evaluates in-process. Results and CSV ordering do not depend
on it.
symmetric_bucket : bool
When True in bucket mode, evaluate only left/right mirrored
montages (see :func:tit.opt.ex.buckets.build_electrode_mirror_map).
symmetry_eeg_csv : str or None
EEG-position CSV used to derive mirrored electrode pairs. If
unset, it is inferred from the leadfield's net name.
symmetry_pairing : str
Symmetry interpretation for bucket mode when symmetric_bucket
is True. "within_pairs": each channel's minus electrode is the
mirror of its plus electrode. "cross_pairs" instead (not in
addition): channel 3 mirrors channel 1 and channel 4 mirrors
channel 2 (e3+ = mirror(e1+), e3- = mirror(e1-), and so on).
Raises¶
ValueError
If current_mA or roi_radius is not a finite number above 0,
if symmetric_bucket is set
with pool electrodes, if symmetry_pairing is not one of
"within_pairs"/"cross_pairs", or if roi_coordinate_space
is not "subject" or "mni".
Examples¶
from tit.opt import MExConfig cfg = MExConfig( ... subject_id="ernie", ... leadfield_hdf="ernie_leadfield_EEG10-10_UI_Jurak_2007.hdf5", ... roi_name="L-Insula", ... electrodes=MExConfig.PoolElectrodes( ... electrodes=["Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2"]), ... current_mA=1.0, ... ) cfg.roi_name, cfg.current_mA ('L-Insula.csv', 1.0)
Then run_m_ex_search(cfg).
See Also¶
MExResult : Result container returned by :func:~tit.opt.mex.mex.run_m_ex_search.
tit.opt.mex.mex.run_m_ex_search : Consumes this config.
tit.calc.envelope_max : Maximum of the TI/mTI envelope over direction.
AtlasROI
dataclass
¶
AtlasROI(atlas_path: str, label: int | None = None, atlas_space: Literal['subject', 'mni'] = 'subject')
Volumetric atlas or mask ROI, unioned with the spherical center.
Attributes¶
atlas_path : str
Path to a volumetric atlas or mask file -- NIfTI (.nii,
.nii.gz) or FreeSurfer (.mgz), e.g. one discovered by
:class:tit.atlas.voxel.VoxelAtlasManager.
label : int or None
Integer label to select within the atlas (elements are
included where the voxel value equals label). None
treats the whole file as a binary mask (voxel value > 0).
atlas_space : str
Space of the atlas file, "subject" (default) or "mni".
MNI masks are warped to subject space before the search.
Raises¶
ValueError
If atlas_space is not "subject" or "mni".
BucketElectrodes
dataclass
¶
BucketElectrodes(e1_plus: list[str], e1_minus: list[str], e2_plus: list[str], e2_minus: list[str], e3_plus: list[str], e3_minus: list[str], e4_plus: list[str], e4_minus: list[str])
Separate electrode lists for each of the four channels' electrodes.
Attributes¶
e1_plus, e1_minus, e2_plus, e2_minus, e3_plus, e3_minus, e4_plus, e4_minus : list of str Candidate electrodes for each channel's Ek+ / Ek- position.
MExResult
dataclass
¶
MExResult(success: bool, output_dir: str, n_combinations: int, results_csv: str | None = None, config_json: str | None = None)
Result from a multipolar 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 eight-electrode 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¶
MExConfig : Configuration consumed by :func:~tit.opt.mex.mex.run_m_ex_search.
tit.opt.mex.mex.run_m_ex_search : Returns this result.
run_ex_search ¶
Run an exhaustive two-pair TI search over a precomputed leadfield.
Enumerates every electrode-pair combination allowed by
config.electrodes and every current split allowed by
total_current/current_step/channel_limit, scores each
against the ROI, and writes a ranked CSV plus a config JSON to the
run directory under the subject's ex-search/ folder.
Parameters¶
config : ExConfig Fully specified search configuration (subject, leadfield file, ROI, electrode pool or buckets, current sweep).
Returns¶
ExResult
success, output_dir, n_combinations evaluated, and the
results_csv / config_json paths.
Raises¶
ValueError
If no candidate montage can be enumerated (empty pool/buckets or
an over-restrictive symmetry rule), an electrode is not in the
leadfield (raised before it is loaded), a referenced ROI CSV or atlas
file does not exist, or the ROI selects no leadfield mesh element
(raised before the search; no results are written).
FileNotFoundError
If config.leadfield_hdf cannot be found under the subject's
leadfields/ directory.
Examples¶
from tit.opt import ExConfig, run_ex_search cfg = ExConfig( ... subject_id="ernie", ... leadfield_hdf="ernie_leadfield_EEG10-10_UI_Jurak_2007.hdf5", ... roi_name="L-Insula", ... electrodes=ExConfig.PoolElectrodes(electrodes=["Fp1", "Fp2", "C3", "C4"]), ... total_current=2.0, current_step=0.5, channel_limit=1.2, ... run_name="insula_pool", ... ) res = run_ex_search(cfg) # doctest: +SKIP res.success, res.n_combinations, res.results_csv # doctest: +SKIP (True, 18, '.../ex-search/insula_pool/final_output.csv')
See Also¶
ExConfig : Configuration dataclass for this search. ExResult : Returned container. tit.opt.mex.mex.run_m_ex_search : Four-pair (mTI) variant.
Source code in tit/opt/ex/ex.py
run_adaptive_focality ¶
run_adaptive_focality(config: FlexConfig) -> FlexResult
Run step 1 (mean) then step 2 (focality with adaptive thresholds).
Parameters¶
config : FlexConfig
config.mode should be "flex_adaptive" and config.goal
must be "focality" (enforced by
:meth:FlexConfig.__post_init__); config.adaptive supplies
roi_percentage/nonroi_percentage (defaulted to 80/20 if
None).
Both steps write into the job's one run folder (config.output_folder
or a new timestamped one): the mean calibration in its
mean_calibration/ subfolder without a report, the focality run in
the folder itself with the job's report. A previous run's outputs in
that folder are removed first.
Returns¶
FlexResult The step-2 focality run's result, unless step 1 fails -- in which case that (failed) mean-run result is returned unchanged and step 2 never starts.
See Also¶
tit.opt.flex.flex.run_flex_search : The single-run primitive used for both steps. FlexConfig.AdaptiveFocalityConfig : The threshold-percentage config.
Source code in tit/opt/flex/drivers.py
run_pareto_sweep ¶
run_pareto_sweep(config: FlexConfig) -> FlexResult
Run the mean calibration, then every (roi_pct, nonroi_pct) combination.
Persists pareto_results.json and a scatter-plot PNG under the
sweep's base output folder (:func:tit.opt.flex.pareto.save_results),
which also promotes the best-scoring point's optimizer output into
that folder, records the sweep in its flex_meta.json (pareto)
and removes the per-point numbered subdirectories. The mean
calibration runs in mean_calibration/; the points and the
calibration write no report, the sweep folder gets the job's one.
A previous run's outputs in the base folder are removed first.
Parameters¶
config : FlexConfig
config.mode should be "flex_pareto" and config.goal
must be "focality" (enforced by
:meth:FlexConfig.__post_init__); config.pareto supplies
roi_pcts/nonroi_pcts (defaulted if None).
config.output_folder, if set, names the sweep's base folder;
otherwise one is auto-generated under the subject's flex-search
directory.
Returns¶
FlexResult
A summary over the whole sweep: success is True if at least
one grid point completed, output_folder is the sweep's base
folder (post-promotion), function_values lists every
completed point's focality score, and best_value/
best_run_index name the best-scoring point (most negative
focality_score, matching tit.opt.flex.pareto's own
best-run convention) and its position in run order.
See Also¶
tit.opt.flex.pareto : Grid computation and persistence helpers used here. FlexConfig.ParetoSweepConfig : The threshold-percentage-grid config.
Source code in tit/opt/flex/drivers.py
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | |
run_flex_search ¶
run_flex_search(config: FlexConfig, *, report: bool = True) -> 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.
config.mode "flex_adaptive" or "flex_pareto" runs
:func:~tit.opt.flex.drivers.run_adaptive_focality or
:func:~tit.opt.flex.drivers.run_pareto_sweep instead (they write
their own report; report applies to a single run).
A relative output_folder names a run folder under the subject's
flex-search/ directory.
An existing output_folder is re-used in place: the outputs a
previous run wrote there (:data:RUN_OUTPUTS) are removed first, so
the folder describes exactly this run; other files are left alone.
Parameters¶
config : FlexConfig
Fully specified optimization configuration including subject,
ROI definition, electrode geometry, and DE hyperparameters.
report : bool, optional
Write the flex-search report (default). The adaptive and Pareto
drivers pass False for their intermediate steps and write one
report for the job's own folder.
Returns¶
FlexResult
success, output_folder (the run directory under the
subject's flex-search/), per-restart function_values,
best_value and best_run_index.
Raises¶
ValueError
If the subject's m2m directory or head mesh is missing, a
referenced ROI/atlas/EEG-net file is missing, cpus or
n_multistart is below 1, min_electrode_distance is not
positive, enable_mapping is set without eeg_net, or an
"ellipse" electrode has unequal dimensions (flex-search
supports circular electrodes only).
Notes¶
When every restart fails the function does not raise: it returns a
:class:FlexResult with success=False, best_value=inf and
best_run_index=-1. Always check result.success.
Examples¶
from tit.opt import FlexConfig, run_flex_search cfg = FlexConfig( ... subject_id="ernie", goal="mean", postproc="max_TI", current_mA=1.0, ... electrode=FlexConfig.ElectrodeConfig(shape="ellipse", dimensions=[8.0, 8.0]), ... roi=FlexConfig.SphericalROI(x=-35.0, y=5.0, z=5.0, radius=10.0, use_mni=True), ... n_multistart=3, output_folder="insula_mean", ... ) res = run_flex_search(cfg) # doctest: +SKIP res.success, res.best_run_index, len(res.function_values) # doctest: +SKIP (True, 1, 3)
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
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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
run_m_ex_search ¶
Run an exhaustive four-channel (eight-electrode) mTI search over a leadfield.
Enumerates every four-channel combination allowed by
config.electrodes at one fixed current_mA per channel, scores each
candidate with the joint K-pair envelope maximum
(:func:tit.calc.envelope_max), and writes a ranked CSV plus a
config JSON under the subject's m-ex-search/ folder.
Parameters¶
config : MExConfig Fully specified search configuration.
Returns¶
MExResult
success, output_dir, n_combinations evaluated, and the
results_csv / config_json paths.
Raises¶
ValueError
If no candidate montage can be enumerated, an electrode is not in
the leadfield (raised before it is loaded), a referenced ROI CSV or
atlas file does not exist, or the ROI selects no leadfield mesh
element (raised before the search; no results are written).
FileNotFoundError
If config.leadfield_hdf cannot be found under the subject's
leadfields/ directory.
Examples¶
from tit.opt import MExConfig, run_m_ex_search cfg = MExConfig( ... subject_id="ernie", ... leadfield_hdf="ernie_leadfield_EEG10-10_UI_Jurak_2007.hdf5", ... roi_name="L-Insula", ... electrodes=MExConfig.PoolElectrodes( ... electrodes=["Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4"]), ... current_mA=1.0, ... ) res = run_m_ex_search(cfg) # doctest: +SKIP res.success, res.n_combinations # doctest: +SKIP (True, 105)
See Also¶
MExConfig : Configuration dataclass for this search. MExResult : Returned container. tit.opt.ex.ex.run_ex_search : Two-channel (TI) variant.