# PlanTUS -- heuristic planning of transcranial ultrasound (TUS) transducer # placements on a SimNIBS charm head model (Lueckel, Vijayakumar & Bergmann, # Brain Stimulation 18(5), 2025). It scores every skin vertex for distance, # beam angle, skull thickness and no-go regions, lets the user pick one in a # PySide6/VTK viewer, and exports the pose for Brainsight, Localite, k-Plan, # SimNIBS and BabelBrain. BabelBrain's "Run PlanTUS" button runs it. # # Layout facts the spec depends on (checked against the pinned commit): # - No pyproject.toml, setup.py or tags. It is a script tree run as # `python PlanTUS_wrapper.py`, which finds code/ and resources/ next to # itself (resource_path() = dirname(__file__)) and BabelBrain calls it as # /PlanTUS_wrapper.py. So the tree is installed as is to # %%{_datadir}/plantus, the root BabelBrain is pointed at, and # %%{_bindir}/plantus runs the wrapper. # - 2.0 dropped Connectome Workbench entirely; it still writes optional # scene.scene files that wb_view can open, hence Suggests. # - ImageTransform_4kPlan.py needs FSL's fslswapdim and ANTsPy (neither in # Fedora) and serves only k-Plan, a commercial planner: shipped as # documentation, not on PATH. kPlan_results_to_nifti() imports ants lazily # for the same reason and is not required. # - potpourri3d (exact geodesic distances for the no-go margin) is optional # upstream with an approximate fallback; it is not packaged yet. %global commit f790f4ab3b49e48079c2a82710dcdff1d36d1a21 %global shortcommit %(c=%{commit}; echo ${c:0:7}) %global snapdate 20261006 Name: plantus Version: 2.0.0^%{snapdate}git%{shortcommit} Release: 1%{?dist} Summary: Heuristic planning of transcranial ultrasound transducer placements License: MIT URL: https://github.com/mlueckel/PlanTUS # main at 2026-10-06 (CHANGELOG: v2.0.0 plus fixes); upstream has no tags. Source0: %{url}/archive/%{commit}/PlanTUS-%{shortcommit}.tar.gz # Accept the 1.x flag names (--skip_wb_view, --do_only_trajectory) as hidden # aliases. BabelBrain 0.8.x passes --skip_wb_view and stock 2.0 rejects it. # Submitted upstream by the packager. Patch0: plantus-legacy-cli-aliases.patch # create_SimNIBS_position_matrix built a left-handed frame (det -1), so the # Brainsight and BabelBrain exports were mirror images of the planned pose. # Submitted upstream by the packager. Patch1: plantus-simnibs-frame-handedness.patch # The wrapper made a PyQt5 QApplication while the viewer and VTK use PySide6 # when it is installed; the mixture aborts as soon as the viewer opens. # Submitted upstream by the packager. Patch2: plantus-viewer-qt-binding.patch BuildArch: noarch BuildRequires: python3-devel # %%check imports the planning code (which imports simnibs at module level), # the viewer, and runs the wrapper's argument parser. BuildRequires: python3-simnibs BuildRequires: python3-numpy BuildRequires: python3-nibabel BuildRequires: python3-scipy BuildRequires: python3-pyyaml BuildRequires: python3-trimesh BuildRequires: python3-vtk BuildRequires: python3-pyside6 Requires: python3-simnibs Requires: python3-numpy Requires: python3-nibabel Requires: python3-nilearn Requires: python3-scipy Requires: python3-pandas Requires: python3-pyyaml Requires: python3-h5py Requires: python3-trimesh Requires: python3-vtk Requires: python3-pyside6 # charm, which makes the head model PlanTUS starts from, is in python3-simnibs. # Optional Workbench scenes of the metric maps and placements. Suggests: connectome-workbench %description PlanTUS helps users of transcranial ultrasonic stimulation (TUS) choose the most promising transducer placement for sonicating a target region in an individual head. From a SimNIBS charm head model, the T1 image and a target mask, it computes per-vertex skin maps of target distance, beam angle, skin-skull angle, skull thickness and intersections, excludes regions near the eyes, ears and air cavities, and combines them into a composite score. The user picks a placement in an interactive PySide6/VTK viewer (or by vertex number, headless), and PlanTUS writes the transducer pose and an estimated focus for neuronavigation (Brainsight, Localite) and acoustic simulation (BabelBrain, k-Plan, SimNIBS). PlanTUS informs acoustic simulations but does not replace them; placements should be validated with a full simulation. %prep %autosetup -n PlanTUS-%{commit} -p1 rm -rf code/__pycache__ # The tree is run by %%{_bindir}/plantus through python3; nothing in it is # executed directly, so no file keeps an executable bit or a shebang. find . -name '*.py' -exec chmod 0644 {} + sed -i '1{/^#!/d}' PlanTUS_wrapper.py code/*.py %build # Nothing to build. %install mkdir -p %{buildroot}%{_datadir}/%{name} cp -pr PlanTUS_wrapper.py code resources %{buildroot}%{_datadir}/%{name}/ # Transducer configuration templates, read with --config / the YAML argument. cp -p PlanTUS_config_*.yaml %{buildroot}%{_datadir}/%{name}/ %py_byte_compile %{python3} %{buildroot}%{_datadir}/%{name} mkdir -p %{buildroot}%{_bindir} cat > %{buildroot}%{_bindir}/%{name} < help.txt grep -q -- '--skip_viewer' help.txt grep -q -- '--placement_only' help.txt ! grep -q -- '--skip_wb_view' help.txt ! grep -q -- '--do_only_trajectory' help.txt # The planning code and the viewer import from the installed tree, and the # exported pose frames are right-handed (Patch1). PYTHONPATH="$root/code" %{python3} - <<'EOF' import numpy as np import PlanTUS import Viewer # PySide6 + VTK's Qt interactor, offscreen rng = np.random.default_rng(1) for _ in range(50): z = rng.normal(size=3) for roll in (0.0, 37.0, -90.0): M = PlanTUS.create_SimNIBS_position_matrix([10.0, -20.0, 30.0], z, roll) d = np.linalg.det(M[:3, :3]) assert abs(d - 1.0) < 1e-9, (z, roll, d) assert np.allclose(M[:3, 2], z / np.linalg.norm(z)) print("PlanTUS and Viewer import; SimNIBS pose frames right-handed") EOF # The wrapper picks the viewer's Qt binding (Patch2). grep -q 'from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout' "$root/PlanTUS_wrapper.py" %files %license LICENSE.md %doc README.md CHANGELOG.md ImageTransform_4kPlan.py %{_bindir}/%{name} %{_datadir}/%{name}/ %changelog * Tue Oct 13 2026 Morgan Hough - 2.0.0^20261006gitf790f4a-1 - Initial package. Carries three fixes submitted upstream: the 1.x flag names as hidden aliases (so BabelBrain 0.8.x can call it), right-handed SimNIBS, Brainsight and BabelBrain pose exports, and one Qt binding for the viewer.