montage_sources
tit.sim.montage_sources ¶
Resolve non-montage_list.json montage sources into Montage objects.
Extracted from the former PyQt SimulatorTab (as of the v3 audit): the pure logic that turns a flex-search run
selection, or a freehand stim_configs/*.json file, into a concrete
:class:~tit.sim.config.Montage -- the same shape a plain
montage_list.json entry produces via :func:tit.sim.utils.load_montages.
Nothing here touches Qt; every function takes a :class:~tit.paths.PathManager
and plain values instead of reading widget state, so it is reusable from
:mod:tit.server.routes.plan, notebooks, and future non-Qt UIs alike.
Two sources, two resolvers¶
- Flex-search (:func:
resolve_flex_montage): a run underflex-search/<subject>/<run_name>/has anelectrode_positions.jsonwith the optimizer's raw XYZ output.electrode_type="optimized"uses those coordinates directly (Montage.Mode.FLEX_FREE);electrode_type="mapped"maps them onto the nearest electrodes of a named EEG net via the Hungarian algorithm (:func:tit.tools.map_electrodes.map_electrodes_to_net) and uses the resulting labels (Montage.Mode.FLEX_MAPPED), caching the mapping alongside the run. - Freehand (:func:
resolve_freehand_montage): am2m_{subject}/stim_configs/<name>.jsonfile holds anelectrode_positionsdict of label -> XYZ. Every position is used, paired consecutively in file order ((1, 2), (3, 4), ...) -- the same pairing the desktop Simulator applies to a saved placement -- so 4 positions are TI and 8, 12, 16, ... are mTI.
See Also¶
tit.tools.map_electrodes : Hungarian-algorithm electrode-to-net mapping.
tit.sim.utils.load_montages : Resolves plain montage_list.json entries.
tit.sim.config.Montage : The dataclass every resolver here returns.
short_flex_run_id ¶
Build a stable short id from the subject and flex-search run folder.
Mirrors SimulatorTab._short_flex_run_id -- used only as a display
disambiguator (folder names are not unique across subjects), never as a
lookup key on disk.
Source code in tit/sim/montage_sources.py
flex_montage_name ¶
Build a filesystem-safe unique simulation name for a flex-search run.
Mirrors SimulatorTab._build_flex_montage_name exactly (same
sanitisation and length cap), so simulation output directories named
from a flex-search source stay stable across the PyQt and v3 GUIs.
Source code in tit/sim/montage_sources.py
resolve_flex_montage ¶
resolve_flex_montage(pm: PathManager, subject_id: str, run_name: str, electrode_type: str, *, eeg_net: str | None = None, run_id: str | None = None, display_name: str | None = None) -> Montage
Resolve one flex-search run selection into a concrete Montage.
Parameters¶
pm : PathManager
Project path resolver.
subject_id : str
Subject identifier (no sub- prefix).
run_name : str
Flex-search run folder name under flex-search/{subject_id}/.
electrode_type : str
"mapped" (map optimized positions onto eeg_net's nearest
electrodes) or "optimized" (use the raw optimizer XYZ output
directly, no net needed).
eeg_net : str or None, optional
EEG net CSV filename (e.g. "GSN-HydroCel-185.csv"). Required
when electrode_type is "mapped".
run_id : str or None, optional
Precomputed :func:short_flex_run_id; recomputed if omitted.
display_name : str or None, optional
User-facing label for the resulting Montage.display_name.
Defaults to "{run_name} | {run_id} | {electrode_type}".
Returns¶
Montage
mode=FLEX_MAPPED (net labels) for "mapped", or mode=FLEX_FREE (XYZ
coordinates) for "optimized"; one pair per stimulation channel, paired by
:func:tit.catalog.pair_by_channel (the optimiser's channel_array_indices), so
a 4-channel (mTI) run keeps all four pairs, as the Simulator's flex rows do.
Raises¶
ValueError
If electrode_type is unknown, eeg_net is missing for
"mapped", the run folder / electrode_positions.json /
EEG-net CSV cannot be found, or fewer than 4 electrodes are
available.
A net the run is already mapped to (its electrode_mapping_<net>.json) is read, not
re-mapped: the Hungarian mapping runs, and the cache is written, only the first time.
Source code in tit/sim/montage_sources.py
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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 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 | |
flex_currents ¶
A flex run's own simulation currents (mA) and where they came from.
The run's optimised current_split, else its current_mA per channel, else 1 mA per
channel. The Simulator page applies the same order to the catalog's manifest in
desktop/src/renderer/pages/simulator/FlexTab.tsx (flexCurrents); both are pinned by
tests/fixtures/flex_currents.json.
Source code in tit/sim/montage_sources.py
resolve_flex_simulation ¶
resolve_flex_simulation(pm: PathManager, subject_id: str, flex_run: str | None = None, *, eeg_net: str | None = None, intensities: list[float] | None = None) -> dict[str, Any]
What the Simulator does for a flex row: the run's electrodes and currents as one montage.
The single source for turning a finished flex-search run into a SimulationConfig
montage -- POST /api/sim-from-flex (the agent plugin's simulate_flex_result) and a
proposal's deferred sim_from_flex step (:mod:tit.server.proposals) both call it.
The run is flex_run (a folder name or path; its basename is used), else the newest run.
Placement is eeg_net's labels (mapped on demand, cached beside the run), else the first
net the run is already mapped to, else the optimised XYZ (flex_free). Currents are
intensities, else the run's optimised split, else its current_mA per channel, else
1 mA per channel.
Returns {flex_run, eeg_net, placement, intensities, intensities_from, montage} where
montage is a serialised Montage named after the run. Raises :class:ValueError for
an unknown subject, no run, an unknown run or a run without usable positions.
Source code in tit/sim/montage_sources.py
resolve_freehand_montage ¶
resolve_freehand_montage(pm: PathManager, subject_id: str, name: str) -> Montage
Resolve one freehand stim-config name into a concrete Montage.
Every coordinate in electrode_positions is used, paired consecutively
in file order -- positions 1+2 are pair 1, 3+4 pair 2, and so on -- which
is how the desktop Simulator turns a saved placement into a job
(MontageManager.setRowFreehand). Four positions give TI; 8, 12, 16 ...
give mTI.
Raises¶
ValueError
If the subject has no m2m/stim_configs directory, no config
named name exists, or its position count is not an allowed pair
count (:func:tit.constants.is_valid_pair_count of half of it).