PlanTUS placement pipeline – step-by-step¶
⚠️ Experimental. PlanTUS placement is under active development. Results should be verified manually before use in a study.
This document describes the sequence of operations performed when
placement.mode = 'plantus' is set in a PRESTUS configuration.
Overview¶
Given a T1-weighted image, a SimNIBS head mesh, a target-ROI mask, and a transducer configuration YAML, PlanTUS ranks every skin surface vertex as a candidate transducer position.
Two modes are available, controlled by parameters.simulation.interactive:
| Mode | Script | Vertex selection |
|---|---|---|
Headless (interactive = 0, default) |
prestus_plantus_launcher.py |
Automatic — weighted composite score, no display required |
Interactive (interactive = 1) |
prestus_plantus_gui_shim.py |
Manual — five scoring maps displayed in Connectome Workbench; user clicks a vertex and confirms at the console prompt |
Both modes compute the same scoring maps (steps 2–4 below) and write the
same output files. Interactive mode additionally requires pynput and
Connectome Workbench wb_view; headless mode requires neither.
The original PlanTUS_wrapper.py (Mainz group, available separately at
placement.plantus.script_path) is not called directly because it has
hard-coded paths and unconditionally imports pynput. The two PRESTUS scripts
above replicate the identical computation while accepting CLI arguments.
Step-by-step pipeline¶
1 Parse inputs¶
| Input | Description |
|---|---|
t1_filepath |
Subject T1.nii.gz (SimNIBS output) |
simnibs_mesh_filepath |
Head mesh sub-XXX.msh (CHARM output) |
target_mask_filepath |
Binary NIfTI sphere centred on the target |
tx_config_yaml |
Transducer + placement parameters (written by PRESTUS) |
The YAML is a flat key–value file.
Key fields: max_distance, min_distance, transducer_diameter,
plane_offset, additional_offset, focal_distance_list, flhm_list,
five weight_* scalars.
2 Convert SimNIBS mesh to surfaces¶
PlanTUS.convert_simnibs_mesh_to_surface extracts triangulated .surf.gii
files from the volumetric .msh mesh by tissue tag:
| Surface | SimNIBS tags | File |
|---|---|---|
| Skin | 1005 | skin.surf.gii |
| Skull | 1007, 1008 | skull.surf.gii |
SimNIBS also produces skull.stl (used in the skull-entry-angle metric below).
3 Avoidance mask (create_avoidance_mask)¶
Identifies skin vertices that must not be used as transducer positions:
- Air cavities / sinuses – binarise
final_tissues.nii.gz, fill holes withwb_command -volume-fill-holes, subtract to isolate air; tessellate to STL with FreeSurfermri_tessellate; any skin vertex whose outward normal ray intersects an air cavity is masked out. - Eyes – extract eye mesh (tag 1006), find left/right centres via k-means on x-coordinates; mask out all skin vertices within 30 mm of each eye centre.
- Ears (tragus) – read LPA/RPA from
eeg_positions/Fiducials.csv; shift 1.5 cm posterior; mask out all vertices within 15 mm. - Erosion – the binary avoidance metric is eroded by
transducer_radiusmm to leave a safety margin.
Vertices with avoidance == 0 are excluded from the composite score.
External tools required:
fslmaths,wb_command,mri_tessellate,mris_convert. Iffinal_tissues.nii.gzis absent (e.g. CHARM not run) the avoidance step is skipped with a warning and all vertices remain eligible.
4 Per-vertex scoring metrics¶
For every skin vertex i four metrics are computed:
4a Skin–target distance d_i¶
Euclidean distance from skin vertex i to the centre of mass of the target
mask (from PlanTUS.roi_center_of_gravity).
Lower is better.
4b Skin–target angle α_i¶
Angle (degrees) between the skin surface normal at i and the vector from i
to the target centre of mass.
Lower is better (normal points towards target → good sonication path).
4c Skin–target intersection x_i¶
The outward-pointing skin normal at i is cast as a ray of length 200 mm into
the volume. The ray is intersected with the 3-D surface of the target mask STL
(generated by PlanTUS.stl_from_nii). The metric is the total path length of
the ray inside the target (chord length for one entry–exit pair, sum of
chords for multiple pairs).
Higher is better.
4d Skin–skull angle β_i¶
The skin normal ray is intersected with the skull STL (ray length 40 mm). The
closest skull surface vertex to the intersection point is found; the angle
between the skin normal and the skull normal at that vertex is computed.
Lower is better (perpendicular skull entry → lower aberration).
Note: A fifth metric, skull thickness, appears in the BabelBrain weight configuration but is not yet implemented in the open-source PlanTUS release. The corresponding weight (
weight_skull_thickness) is accepted in the YAML but the weight is currently absorbed into the other four metrics proportionally.
5a Headless mode — automatic composite scoring¶
Each metric is normalised to [0, 1] (min–max over all finite values; NaN or infinite values are set to 1 = worst). The intersection metric is inverted (higher intersection → lower normalised cost).
Composite cost for vertex i:
score_i = ( w_dist × norm(d_i)
+ w_ang × norm(α_i)
+ w_inter × (1 − norm(x_i))
+ w_skull × norm(β_i) )
÷ (w_dist + w_ang + w_inter + w_skull)
Vertices with avoidance == 0 or d_i > max_distance receive score = inf.
The vertex with the minimum composite score is selected as the transducer position.
5b Interactive mode — GUI vertex selection¶
When parameters.simulation.interactive = 1, a planning scene is built from
the four normalised metric maps and loaded into Connectome Workbench (wb_view).
The user visually inspects the maps on the inflated skin surface, clicks on a
vertex, and at the console prompt types yes to confirm placement (or no to
continue browsing). The step repeats until the user closes wb_view.
The interactive mode requires:
- pynput Python package (mouse listener for reading wb_view output)
- Connectome Workbench wb_view — either on PATH or specified via
placement.plantus.connectome_wb_path
- A MATLAB desktop session (usejava('desktop') == true)
Multiple vertex confirmations in one session are possible; PRESTUS reads the
most recently written *Localite.mat.
6 Placement geometry (prepare_acoustic_simulation)¶
For the best vertex v:
-
Determine beam direction: if the skin normal at v intersects the target (
x_v > 0), use the negated skin normal; otherwise use the negated skin-to-target vector. Both are unit vectors pointing into the head. -
Compute transducer centre:
transducer_centre = skin_coordinate_v
− (plane_offset + additional_offset) × beam_direction
plane_offset = distance from radiating surface to transducer exit plane.
additional_offset = gel pad / standoff thickness.
- Build a 4 × 4 Localite position matrix (RAS mm) with
PlanTUS.create_Localite_position_matrix: - Column 3 (translation) =
transducer_centre - Column 0 (x-axis) =
beam_direction(unit vector) -
Columns 1–2 = orthonormal frame computed via Gram–Schmidt
-
Save outputs to
<mesh_dir>/PlanTUS/<target_name>/vtx<N>/:
| File | Contents |
|---|---|
*_Localite.mat |
position_matrix (4×4, RAS mm) — used by PRESTUS |
*_Localite.txt |
Plain-text copy |
*_kPlan.mat |
Same matrix in k-Plan convention (m, axes permuted) |
*_Localite_XML.txt |
Fake Localite XML snippet |
focus_position_matrix_*.txt |
Focus point transform |
transducer_*.surf.gii |
Transducer surface model at position |
7 PRESTUS reads the output¶
position_transducer_plantus.m searches for the most recent *Localite.mat
under <mesh_dir>/PlanTUS/<target_name>/, loads position_matrix, and passes
it to localite_matrix_to_positions to convert the 4×4 RAS matrix into
trans_pos and focus_pos in voxel coordinates.
After the placement coordinates are resolved, a T1 overlay plot (plot_placement_t1_overlay) is generated automatically and written to the subject output folder. It shows the final transducer position and target overlaid on orthogonal T1 slices.
Coordinate conventions¶
| Frame | Units | Description |
|---|---|---|
| Voxel / grid | integer indices | PRESTUS simulation grid |
| RAS+ (subject) | mm | T1 image world space; used by Localite matrix |
| MNI | mm | Template space; target specified here, mapped to RAS via SimNIBS |
| k-Plan | m, permuted axes | Not used by PRESTUS |
External dependencies¶
| Tool | Headless | Interactive | Used in step |
|---|---|---|---|
| SimNIBS Python (nibabel, scipy, nilearn) | ✓ | ✓ | All steps |
PlanTUS Python library (PlanTUS.py) |
✓ | ✓ | All steps |
| PyYAML (or built-in fallback) | ✓ | ✓ | YAML config loading |
FSL fslmaths |
✓ | ✓ | Avoidance mask – tissue binarisation |
Connectome Workbench wb_command |
✓ | ✓ | Avoidance mask – hole filling |
FreeSurfer mri_tessellate / mris_convert |
✓ | ✓ | Avoidance mask – air mesh |
Connectome Workbench wb_view |
— | ✓ | Interactive scene display |
pynput Python package |
— | ✓ | Mouse click listener for wb_view |
If the avoidance-mask external tools are unavailable, the launcher proceeds without masking and prints a warning.
Installing pynput for interactive mode¶
# In the SimNIBS conda environment:
conda activate simnibs_v4.6.0
pip install pynput