Ex-Search TI Optimization Pipeline
Ex-Search is the toolbox’s exhaustive montage optimizer: it evaluates every electrode combination (× every current split) you allow, on a precomputed leadfield, and ranks the results. Unlike Flex-Search, which freely positions electrodes on the scalp with a stochastic optimizer, ex-search is restricted to the electrodes of an EEG net — but within that space it is guaranteed to find the global optimum, and each candidate costs milliseconds.
Prerequisites: Leadfield and EEG Net
Every ex-search runs against a leadfield matrix for one subject and one EEG net. An Ex job’s settings dialog opens with a Leadfield strip: choose an EEG net, and the strip shows the leadfield’s size and the electrode it was computed with if it exists, or “No leadfield for this net yet” and Generate (≈ N min) (the estimate is for that net on this machine, one FEM solve per electrode). Generate, here or as Generate leadfield in the job’s row, first asks for the electrode — Shape, Dimensions (width × height, mm) and Gel thickness, starting at the Simulator’s defaults (ellipse, 8 × 8 mm, 1 mm gel, 2 mm rubber); the ? beside Electrodes explains each — and queues the leadfield job when you click Generate. A row with no net yet takes the subject’s first computed leadfield, or the first net when none is computed. Leadfields are 2–20 GB depending on the number of electrodes. The nets are the ones pre-processing placed on the head model (see Available EEG Nets).
EEG 10-20 Okamoto 2004 electrode configuration - widely used standard with 32 electrodes
GSN-HydroCel 256 electrode configuration - high-density net for precise targeting
Leadfields are generated with tissues=[1, 2] (white + grey matter).
A leadfield is the field of one electrode placed at every position of the net, so its electrode is part of what it is: the search scores montages with that electrode’s size and gel. The electrode is recorded beside the leadfield in <subject>_leadfield_<net>.json and shown with it in the Net / leadfield cell (for example “GSN-HydroCel-185 · 2.0 GB · ellipse 8×8 mm, gel 1 mm”). A leadfield with no such record shows “electrode not recorded”; it loads and runs, but its electrode is unknown. There is one leadfield per subject and net, so a leadfield with another electrode for the same net replaces it: from Python, LeadfieldConfig(subject_id, eeg_net, electrode_shape=…, electrode_dimensions=[w, h], gel_thickness=…, rubber_thickness=…, overwrite=True).
In the application
Ex-search is the Ex method of an Optimizer job row (⌘2): pick the subject, the net/leadfield, then open the row's settings.
Open an Ex job’s settings from its row (the settings button, or double-click the row). The dialog has:
- Leadfield: the EEG net and its leadfield (see above).
- Target: Subcortical, Saved or NIfTI mask (see Defining the Target).
- Electrodes: Count (2 channels (TI), four electrodes, or 4 channels (mTI), eight), Search space (Bucketed or All combinations) and the electrode buckets or pool (see Defining the Search Space). The line under the section counts the electrodes, montages, current splits and iterations.
- Current: Total current, Step and Channel limit (see Current ratios). With 8 electrodes this section is Carriers instead.
The Ex job settings with 2 channels (TI) and bucketed electrodes.
Defining the Target (ROI)
The Target section offers three choices:
- Saved uses spherical ROIs saved for the subject: tick one or more, or create one with Add ROI (a new subject shows “No saved ROIs for this subject yet”). Radius sets the sphere (1–10 mm, default 3 mm) and Space says whether the centres are Subject or MNI coordinates. Combine selected ROIs into one target unions the selected spheres into a single Ex search; without it each selected ROI runs its own search.
- Subcortical selects regions from a volumetric atlas.
- NIfTI mask imports a local
.niior.nii.gzfile into the project. Positive voxels define the target. Subject-space masks are used directly; MNI masks use the subject’s SimNIBS registration inm2m_<subject>/toMNI, with nearest-neighbor resampling. The conformation affine alone is not an MNI registration.
Electrodes is a separate section: set Count to 2 channels (TI) or 4 channels (mTI), then configure the search space. Channel k is electrodes Ek+ and Ek-, so the buckets are E1+, E1-, E2+, E2- (and E3±, E4± for mTI).
Under the hood, ROI resolution OR-folds a mixed list of CSV centers, whole NIfTI/MGZ masks (voxel value > 0), and (path, label) atlas-region selections (voxel value == label) into a single region.
Defining the Search Space
Bucketed
Electrodes ▸ Search space ▸ Bucketed pre-assigns electrodes to channel positions:
- E1+: Electrodes for positive channel 1
- E1-: Electrodes for negative channel 1
- E2+: Electrodes for positive channel 2
- E2-: Electrodes for negative channel 2
Combinations: at most \(N_1 \times N_2 \times N_3 \times N_4\), where \(N_i\) is the number of electrodes in bucket \(i\) (a choice that uses one electrode twice is skipped, and physically identical montages are evaluated once: TI ignores which electrode of a channel is + or − and, when the current splits are symmetric, which channel is which, so overlapping buckets give fewer combinations).
All combinations
Electrodes ▸ Search space ▸ All combinations pools the electrodes: any of them can be assigned to any channel position, with each electrode used only once per montage.
Combinations: \(\binom{N}{4} \times 3\), where \(N\) is the total number of electrodes — every 4-electrode subset split into two pairs in its three ways (a pair’s polarity and the channel order do not change the envelope); ×2 when the current splits are not mirror-symmetric, because the channel order then matters.
Trade-off: Larger search space and longer compute time but with absolute certainty to find optimal solution with given electrode space.
Symmetric (Bilateral) Constraint
Two-channel bucketed searches can be restricted to left/right-mirrored montages with symmetric_bucket: true, from Python or a JSON config only: the app has no control for it and always sends false. In the app, symmetry is offered for mEx only. The mirror map is read from the EEG-position CSV (symmetry_eeg_csv; inferred from the leadfield name {subject}_leadfield_{net}.hdf5 when omitted). Two pairings:
symmetry_pairing: "within_pairs"— each channel is bilateral:E1-must be the mirror ofE1+andE2-ofE2+(e.g.F7_F8 <> TP7_TP8). Write left candidates in the+buckets and their mirrors in the-buckets.symmetry_pairing: "cross_pairs"— channel 2 is the mirror of channel 1:E2+ = mirror(E1+),E2- = mirror(E1-)(e.g.F5_TP7 <> F6_TP8).
Current ratios
By default each montage runs one balanced split: the Total current shared equally, 1.0 / 1.0 mA at the app’s 2.0 mA. The app starts at total 2.0 mA, step 0.2 mA and channel limit 1.0 mA (half the total); in ExConfig, channel_limit=None (the default) means the same balanced split at any total.
To test splits, raise the Channel limit above half the total: the search then steps channel 1 down from the limit in steps of Step, with neither channel above the limit.
Total 2.0 mA, step 0.2 mA, limit 1.6 mA:
(1.6, 0.4), (1.4, 0.6), (1.2, 0.8), (1.0, 1.0),
(0.8, 1.2), (0.6, 1.4), (0.4, 1.6)
The balanced default is there because an exhaustive search over every 10-10 montage found the best montage almost always best at 1:1 (at most 0.8 % gain from another split for the mean goal): the weaker channel caps the envelope, and the best montages already balance the two channels at the target. See Current-ratio search. The line under Electrodes shows the montage count, the current splits and the total iterations; with one split, iterations equal montages.
Total evaluations = electrode combinations × current ratios. Each montage’s two unit-current channel fields are computed once and rescaled per split — the FEM is linear, so a split costs almost nothing extra.
Example candidate electrode combinations. The final search size is determined by the selected pools or buckets, symmetry constraints, and current splits.
Outputs
Metrics
Every evaluated candidate is scored on the closed-form two-channel TI envelope (TI_max; the math lives on the Field definitions page). The ROI metrics use every leadfield element in the region, grey and white matter alike (the leadfield covers both); only TImean_GM is restricted to grey matter:
| Key | Meaning |
|---|---|
TImax_ROI |
Max over ROI elements |
TImean_ROI |
Volume-weighted mean over ROI elements |
TImean_GM |
Volume-weighted mean over GM elements (SimNIBS tag 2) |
Focality |
TImean_ROI / TImean_GM |
Composite_Index |
TImean_ROI × Focality |
HFpeak_Focality |
Focality computed on hf_peak, the peak carrier field of every channel |
TI_Selectivity |
Focality / HFpeak_Focality |
n_elements |
ROI element count |
Focality is the same ratio as the analyzer’s roi_focality, but averaged over the leadfield’s volume elements (grey and white matter, volume-weighted) rather than the analyzer’s surface nodes or voxels, so the two values differ.
HFpeak_Focality is the focality of the stimulation intensity, from the same leadfield, over the same ROI and GM elements and volumes as Focality. TI_Selectivity above 1 means the envelope is more focal than the carriers that produce it; at or below 1 a plain kHz field reaches the target as selectively. Both are reported only: Composite_Index and the ranking do not use them. A candidate with no ROI or grey-matter elements, or no carrier field in grey matter, has empty HFpeak_Focality and TI_Selectivity cells, where Focality reads 0. mEx computes hf_peak over all four channels. The analyzer’s hf_peak_focality and ti_selectivity are the same ratios on a full simulation (Modulation and stimulation focality). final_output.csv writes them after Composite_Index; the Evaluated candidates table shows them as those two columns and the report as HF focality and TI selectivity, when the run has them.
Files
Each run writes to derivatives/SimNIBS/sub-{id}/ex-search/{run_name}/:
run_config.json— provenance snapshot of the search configuration.final_output.csv— one row per evaluation. TheMontagecolumn carries the current split as a suffix (e.g.F7_CP5 <> AF7_P3_I1-1.0mA_I2-1.0mA); currents are written with as many decimals as they need (up to 10), so a 0.25 mA step appears as 0.25/0.75/1.25/1.75.- Five PNG figures:
montage_distributions.png(histograms of TImax, TImean, focality),intensity_vs_focality_scatter.png, and three EEG-map figures —electrode_score_heatmap.png(electrode participation across the top-50 montages: colour = summed Composite Index, size = frequency),montage_strength_map.pngandmontage_focality_map.png(the top-150 montages drawn as arcs on the cap, coloured byTImean_ROI/Focality, best montage highlighted). The maps need the net’s EEG-position CSV (resolved likesymmetry_eeg_csv) and are skipped with a log line otherwise; regenerate them for an existing run withsimnibs_python -m tit.opt.ex.results <run_dir> [--eeg-csv CSV].
electrode_score_heatmap.png — which electrodes recur in the best montages (16,807-evaluation run)
montage_strength_map.png — top-150 montages by ROI strength
Example Results
(example output from v2.4.0) Top 5 of 16,807 evaluations by Composite_Index (sub-ernie, EEG10-10 Jurak leadfield, 5 mm spherical ROI at MNI (−38, 5, 0)). Bucket search with 7 candidate electrodes per position (7⁴ = 2,401 electrode combinations) × 7 current splits (2 mA total, 0.25 mA step) — 13.6 min on 12 cores (0.05 s each):
| Montage | Current_Ch1_mA | Current_Ch2_mA | TImax_ROI | TImean_ROI | TImean_GM | Focality | Composite_Index |
|---|---|---|---|---|---|---|---|
| F7_TP9 <> F3_P1 | 1.0 | 1.0 | 0.3561 | 0.2066 | 0.0959 | 2.1553 | 0.4454 |
| F7_TP9 <> F3_PO3 | 1.0 | 1.0 | 0.3766 | 0.2103 | 0.1024 | 2.0545 | 0.4320 |
| FT7_TP9 <> FC3_P1 | 1.0 | 1.0 | 0.3637 | 0.1819 | 0.0771 | 2.3585 | 0.4291 |
| F7_TP7 <> F3_P3 | 1.0 | 1.0 | 0.3451 | 0.2109 | 0.1050 | 2.0091 | 0.4237 |
| F7_TP9 <> F3_O1 | 1.0 | 1.0 | 0.3766 | 0.2103 | 0.1047 | 2.0092 | 0.4225 |
`montage_distributions.png` from the same run: TImax, TImean and focality across all 16,807 evaluations.
`intensity_vs_focality_scatter.png`: every evaluation plotted as ROI mean intensity against focality, coloured by `Composite_Index`. With ~17k candidates the Pareto front emerges along the upper-right edge — no montage improves intensity without giving up focality beyond it. Equal 1.0/1.0 mA splits populate the high-intensity end; strongly unequal splits (0.2/1.8) collapse to the low-intensity cluster on the left.
Symmetric example
(example output from v2.4.0) within_pairs, 7 bilateral candidates per channel × 7 current splits = 343 evaluations on sub-ernie (18 s):
| Montage | Ch1 mA | Ch2 mA | TImax_ROI | TImean_ROI | TImean_GM | Focality | Composite_Index |
|---|---|---|---|---|---|---|---|
| FT7_FT8 <> TP7_TP8 | 0.8 | 1.2 | 0.4371 | 0.2290 | 0.1150 | 1.9909 | 0.4560 |
| FT7_FT8 <> CP5_CP6 | 0.8 | 1.2 | 0.4404 | 0.2285 | 0.1145 | 1.9949 | 0.4558 |
| F7_F8 <> TP7_TP8 | 0.8 | 1.2 | 0.3964 | 0.2021 | 0.0938 | 2.1540 | 0.4353 |
Top bilateral montages by ROI strength
Intensity vs focality, 343 evaluations
Scene pane
The pane follows the active job row:
- Subcortical targets are drawn, and clicking a region adds it to the target or removes it. A subject-space target is shown on the packaged reference head, because the subject’s head model carries no copy of a volumetric atlas (the pane says which head it shows); an MNI-space target is shown on MNI152. A volume only the subject has, such as a mask under its head model’s
masks/folder, has no surface on any head, so the pane shows the subject’s own scalp without a brain and says so. - Saved ROI and NIfTI mask targets are not drawn in the pane. It offers Open target in TetraVox instead, which opens the target on this subject in the TetraVox app.
- With an EEG net chosen, a Subcortical row’s pane also draws that net’s cap, each electrode coloured by the bucket it is in. Switch the pane’s toolbar control from Target to Electrodes, and a click on an electrode adds it to or removes it from the active bucket: the one you last focused in the form or picked in the pane’s bucket legend.
Results
Open a finished Ex or mEx run in Results ▸ Evaluated candidates. The table is the run’s final_output.csv: the same columns in the same order and every cell as the file has it, one row per montage, named by its Montage, ranked by Composite_Index, highest first. Click a column header to sort every montage by that column (an arrow marks it); click it again to reverse the order. Beside it, a plot shows each montage’s Vertical axis column against its TImean_ROI; the vertical axis is one of the CSV’s metric columns that the run has numbers for (Focality first; never the montage or its currents), and each axis is labelled with its column name and round-number ticks. Outlined points are montages no other montage beats on both axes (lower is better for TImean_GM, higher for the other columns); HFpeak_Focality has no better direction, so it outlines none. Selecting a montage in the table or the plot draws it on the subject’s cap. Use in Simulator turns the selected candidate into an editable Montage row on the Simulator, with its electrodes, net and currents; nothing runs until you press Run simulation.
Report. Every Ex run writes an ex-search report: the winning montage’s electrodes and dose, every montage’s ROI mean against focality in one chart, the top 25 as a sortable table, and the ROI and ranking in words. Rebuild one without re-searching with simnibs_python -m tit.reporting.generators.ex_search <project> <subject> <run name>. mEx runs write no report. See Reports.