Skip to content

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

roi = FlexConfig.SphericalROI(
    x=-42, y=-20, z=55,  # MNI or subject coordinates
    radius=10,            # radius in mm
    use_mni=True,         # True for MNI, False for subject space
)
roi = FlexConfig.AtlasROI(
    atlas_path="/path/to/annotation",
    label=1024,            # integer label from the atlas
    hemisphere="lh",       # "lh" or "rh"
)
roi = FlexConfig.SubcorticalROI(
    atlas_path="/path/to/volumetric_atlas",
    label=10,              # integer label from the atlas
    tissues="GM",          # "GM", "WM", or "both"
)

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

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

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 intensity ratio.

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

AtlasROI(atlas_path: str, label: int, hemisphere: str = 'lh')

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

SubcorticalROI(atlas_path: str, label: int, tissues: str = 'GM')

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.

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
def 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.
    """
    from tit.telemetry import track_operation
    from tit import constants as const

    with track_operation(const.TELEMETRY_OP_FLEX_SEARCH):
        return _run_flex_search_inner(config)

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

AtlasROI(atlas_path: str, label: int, hemisphere: str = 'lh')

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

SubcorticalROI(atlas_path: str, label: int, tissues: str = 'GM')

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

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.

run_ex_search(config: ExConfig) -> ExResult

Run exhaustive search from a typed config object.

Source code in tit/opt/ex/ex.py
def run_ex_search(config: ExConfig) -> ExResult:
    """Run exhaustive search from a typed config object."""
    from tit.telemetry import track_operation
    from tit import constants as const

    with track_operation(const.TELEMETRY_OP_EX_SEARCH):
        return _run_ex_search_inner(config)

tit.opt.config.ExConfig.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.

tit.opt.config.ExConfig.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.

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
def __init__(
    self,
    subject_id: str,
    electrode_cap: str = "EEG10-10",
    progress_callback: Callable | None = None,
    termination_flag: Callable[[], bool] | None = None,
) -> None:
    self.subject_id = subject_id
    self.electrode_cap = electrode_cap
    self._progress_callback = progress_callback
    self._termination_flag = termination_flag
    self.pm = get_path_manager()

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
def generate(
    self,
    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*.
    """
    from simnibs import sim_struct
    import simnibs

    tissues = [1, 2]
    m2m_dir = Path(self.pm.m2m(self.subject_id))
    output_dir = Path(
        output_dir or self.pm.ensure(self.pm.leadfields(self.subject_id))
    )
    output_dir.mkdir(parents=True, exist_ok=True)

    self._cleanup(output_dir, m2m_dir)

    tdcs_lf = sim_struct.TDCSLEADFIELD()
    tdcs_lf.fnamehead = str(m2m_dir / f"{self.subject_id}.msh")
    tdcs_lf.subpath = str(m2m_dir)
    tdcs_lf.pathfem = str(output_dir)
    tdcs_lf.interpolation = None
    tdcs_lf.map_to_surf = False
    tdcs_lf.tissues = tissues
    tdcs_lf.eeg_cap = str(
        Path(self.pm.eeg_positions(self.subject_id)) / f"{self.electrode_cap}.csv"
    )

    if self._termination_flag and self._termination_flag():
        raise InterruptedError("Leadfield generation cancelled before starting")

    self._log(
        f"Generating leadfield for {self.subject_id} (cap={self.electrode_cap})"
    )
    simnibs.run_simnibs(tdcs_lf)

    if self._termination_flag and self._termination_flag():
        raise InterruptedError("Leadfield generation cancelled after SimNIBS")

    hdf5_path = next(output_dir.glob("*.hdf5"))
    self._log(f"Leadfield ready: {hdf5_path}")
    return hdf5_path

list_leadfields

list_leadfields(subject_id: str | None = None) -> list[tuple[str, str, float]]

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
def list_leadfields(
    self, subject_id: str | None = None
) -> list[tuple[str, str, float]]:
    """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.
    """
    sid = subject_id or self.subject_id
    leadfields_dir = Path(self.pm.leadfields(sid))

    out: list[tuple[str, str, float]] = []
    for item in leadfields_dir.iterdir():
        if item.suffix != ".hdf5":
            continue

        stem = item.stem
        if "_leadfield_" in stem:
            net_name = stem.split("_leadfield_", 1)[-1]
        elif stem.endswith("_leadfield"):
            net_name = stem[: -len("_leadfield")]
        else:
            net_name = stem

        for prefix in (f"{sid}_", sid):
            if net_name.startswith(prefix):
                net_name = net_name[len(prefix) :]
                break

        net_name = net_name.strip("_") or "unknown"
        out.append((net_name, str(item), item.stat().st_size / (1024**3)))

    return sorted(out)

get_electrode_names

get_electrode_names(cap_name: str | None = None) -> list[str]

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.

Source code in tit/opt/leadfield.py
def get_electrode_names(self, cap_name: str | None = None) -> list[str]:
    """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.
    """
    from simnibs.utils.csv_reader import eeg_positions

    cap_name = cap_name or self.electrode_cap
    eeg_pos = eeg_positions(str(self.pm.m2m(self.subject_id)), cap_name=cap_name)
    return sorted(eeg_pos.keys())