Skip to content

config

tit.opt.config

Configuration dataclasses for TI optimization.

Pure Python -- no SimNIBS, numpy, or heavy dependencies. Mirrors the tit.sim.config pattern.

Public API

FlexConfig Full configuration for flex-search (differential-evolution) optimization. FlexResult Result container for a completed flex-search run. ExConfig Full configuration for exhaustive (grid) search optimization. ExResult Result container for a completed exhaustive search run. MExConfig Full configuration for multipolar (4-pair) exhaustive search. MExResult Result container for a completed multipolar exhaustive search run.

See Also

tit.opt.flex.flex.run_flex_search : Consumes :class:FlexConfig. tit.opt.ex.ex.run_ex_search : Consumes :class:ExConfig. tit.opt.mex.mex.run_m_ex_search : Consumes :class:MExConfig.

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, 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)

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 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"). Only used by the ROC-based "focality" goal. None (or a placeholder such as "dynamic") lets SimNIBS supply its own defaults -- but that fallback exists only inside SimNIBS, so combining goal="focality" with optimize_current_ratio makes explicit numeric thresholds required: the ratio search scores candidates with its own ROC objective, which has no defaults. 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" requires explicit thresholds, 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 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. 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 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. 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.

Raises

ValueError If goal is "focality" with non_roi_method "specific" but non_roi is None, if thresholds contains non-numeric values, 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" without explicit thresholds, or if detailed_results is combined with a callable-goal configuration (goal="focality_tf" or optimize_current_ratio).

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 peak 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) / p95(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 amplitude (direction-independent). DIR_TI_NORMAL : str TI component normal to the cortical surface. DIR_TI_TANGENTIAL : str TI component tangential to the cortical surface.

NonROIMethod

Bases: StrEnum

Non-ROI specification method for focality optimization.

Attributes

EVERYTHING_ELSE : str Use all mesh elements outside the ROI. SPECIFIC : str Use an explicitly defined non-ROI region.

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.

AtlasROI dataclass

AtlasROI(atlas_path: str | list[str], label: int | list[int], hemisphere: str | list[str] = 'lh')

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.

SubcorticalROI dataclass

SubcorticalROI(atlas_path: str | list[str], label: int | list[int], 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 within the volumetric atlas. 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.

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.

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.

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)

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. 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 is always subject 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 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, or if roi_coordinate_space is not "subject" or "mni".

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)

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).

BucketElectrodes dataclass

BucketElectrodes(e1_plus: list[str], e1_minus: list[str], e2_plus: list[str], e2_minus: list[str])

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.

PoolElectrodes dataclass

PoolElectrodes(electrodes: list[str])

Single electrode pool -- all positions draw from the same set.

Attributes

electrodes : list of str List of electrode names available for any channel position.

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, channels: list[tuple[list[int], list[int]]] | 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, symmetric_bucket: bool = False, symmetry_eeg_csv: str | None = None, symmetry_pairing: str = 'within_pairs')

Full configuration for multipolar (4-pair, 8-electrode) exhaustive search.

Evaluates every valid combination of four bipolar electrode pairs from a user-defined pool or bucket set, at one fixed current per pair, and scores each candidate with the verified N>2 mTI envelope (:func:tit.calc.get_mTI_vectors).

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 four separate per-pair buckets (:class:BucketElectrodes). A plain dict is auto-converted in __post_init__. current_mA : float Current in mA delivered by each of the four pairs. channels : list of (list of int, list of int), or None Carrier grouping passed to :func:tit.calc.get_mTI_vectors. None treats the four pairs as two independent TI channels (equivalent to [([0], [1]), ([2], [3])]); an explicit grouping such as [([0, 2], [1, 3])] instead treats all four pairs as one channel sharing two carriers (Lee et al. 2022). These give materially different fields, so the grouping must be chosen deliberately -- the quasi-static field solve has no frequency term, so this grouping is the only place carrier assignment is expressed. roi_radius : float Spherical ROI radius in mm for the target region. 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 is always subject space. run_name : str or None Optional name for this run. Defaults to a datetime stamp. symmetric_bucket : bool When True in bucket mode, evaluate only left/right mirrored electrode pairs (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" mirrors each pair's plus/minus electrodes independently; "cross_pairs" additionally mirrors pair 1<->3 and pair 2<->4.

Raises

ValueError If current_mA is non-positive, 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".

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.get_mTI_vectors : Modulation-amplitude envelope; consumes channels.

AtlasROI dataclass

AtlasROI(atlas_path: str, label: int | None = None)

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).

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 bipolar pairs.

Attributes

e1_plus, e1_minus, e2_plus, e2_minus, e3_plus, e3_minus, e4_plus, e4_minus : list of str Candidate electrodes for each pair's anode/cathode position.

PoolElectrodes dataclass

PoolElectrodes(electrodes: list[str])

Single electrode pool -- all eight positions draw from the same set.

Attributes

electrodes : list of str List of electrode names available for any pair 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.

search_backend_for_mode

search_backend_for_mode(mode)

Return (module path, config class) for an exhaustive-search mode.

Parameters:

Name Type Description Default
mode

:data:SEARCH_MODE_TI or :data:SEARCH_MODE_MTI.

required

Returns:

Type Description

(module_path, config_class); module_path is passed to

simnibs_python -m <module_path> and config_class is the

dataclass to build for that mode.

Raises:

Type Description
ValueError

If mode is not a known search mode.

Source code in tit/opt/config.py
def search_backend_for_mode(mode):
    """Return ``(module path, config class)`` for an exhaustive-search mode.

    Args:
        mode: :data:`SEARCH_MODE_TI` or :data:`SEARCH_MODE_MTI`.

    Returns:
        ``(module_path, config_class)``; *module_path* is passed to
        ``simnibs_python -m <module_path>`` and *config_class* is the
        dataclass to build for that mode.

    Raises:
        ValueError: If *mode* is not a known search mode.
    """
    if mode == SEARCH_MODE_TI:
        return "tit.opt.ex", ExConfig
    if mode == SEARCH_MODE_MTI:
        return "tit.opt.mex", MExConfig
    raise ValueError(f"Unknown search mode: {mode!r}")