# SlicerOpenLIFU — Openwater's Low-Intensity Focused Ultrasound (LIFU) research # platform extension for 3D Slicer. # # This is a PURE SCRIPTED (Python) extension: every module is built with # slicerMacroBuildScriptedModule (no C++ loadable/CLI modules), so it needs no # compilation and no find_package(Slicer) / SlicerConfig.cmake. We therefore do # NOT run its CMake at all — we replicate exactly what the macro installs (the # MODULE_PYTHON_SCRIPTS / MODULE_PYTHON_RESOURCES lists from each module's # CMakeLists, dropped into Slicer's qt-scripted-modules dir with their relative # paths preserved, NO_INSTALL_SUBDIR being the default here). Source1 does that # faithfully by parsing the CMakeLists, so no foundation rebuild is required. # # Runtime Python deps (openlifu, bcrypt, threadpoolctl, requests, segno) are NOT # packaged here: the extension lazy-installs them via slicer.util.pip_install on # first use (OpenLIFULib/lazyimport.py), prompting the user. The modules load and # appear in Slicer without them present. %global slicer_inst %{_prefix}/lib/slicer %global slicer_subdir Slicer-5.12 Name: 3dslicer-openlifu Version: 1.21.1 Release: 4%{?dist} Summary: OpenLIFU (Low-Intensity Focused Ultrasound) extension for 3D Slicer # Extension source is AGPL-3.0; see the bundled LICENSE.txt. License: AGPL-3.0-or-later URL: https://github.com/OpenwaterHealth/SlicerOpenLIFU Source0: %{url}/archive/refs/tags/v%{version}/SlicerOpenLIFU-%{version}.tar.gz # Faithful installer: parses each module's CMakeLists for MODULE_PYTHON_SCRIPTS / # MODULE_PYTHON_RESOURCES and installs them into qt-scripted-modules, emitting the # %%files list (owns OpenLIFU-specific dirs, leaves shared dirs to 3dslicer). Source1: olifu-install-modules.py # Qt 6 removed QRegExp/QRegExpValidator. OpenLIFUTransducerLocalization's setup() raised at the # validator, leaving algorithm_input_widget unset, and its observers then failed during other # modules' panel builds ("no attribute 'algorithm_input_widget'"). Patch0: 3dslicer-openlifu-qt6-qregularexpressionvalidator.patch # OpenLIFUHome without a main window (--no-main-window, every headless test run): # setup() added its toolbar to a None main window, and cleanup() looked the toolbar up # by name with slicer.util.findChild, which raises when it is missing, so Slicer exited # with "RuntimeError: Widget with name='CloudSyncToolBar' does not exist". Upstream # fixed both in ac878830 (after 1.21.1, does not apply); this is the minimal backport. Patch1: 3dslicer-openlifu-home-headless-toolbar.patch BuildArch: noarch BuildRequires: python3 Requires: 3dslicer # qt/ctk/vtkAddon scriptability (3D Slicer bug #9) is required for any scripted # module to load; those live in the supporting packages. Requires: ctk-python Requires: python3-vtkAddon # The Python modules the extension actually imports. NONE of these were declared # before: the package installed cleanly, passed repoclosure, and then every # module died at load with ImportError, because a Python import is invisible to # rpm's dependency generator. 26 of the shipped files import openlifu alone. # # The set is not guesswork -- OpenLIFULib/dependency_utils.py has upstream's own # python_requirements_exist(), which returns False unless bcrypt, requests, # segno, threadpoolctl and xarray all import AND find_spec locates openlifu, # openlifu_sdk and openlifu_sdk.ui.simulated_interface. When it returns False the # extension offers to pip install into the user's Slicer, which is exactly what a # distro package must never do; satisfying it with real RPMs is what stops that. Requires: python3-openlifu Requires: python3-openlifu-sdk Requires: python3-bcrypt Requires: python3-requests Requires: python3-segno Requires: python3-threadpoolctl Requires: python3-xarray Requires: python3-numpy Requires: python3-scipy # dvc is NOT required: `from dvc.repo import Repo` is a function-local import in # OpenLIFUHome._ensure_dvc_gdrive_support(), reached only when DVC_REPO_DIR is # set, and upstream's own requirements check does not test for it. It gates the # Google Drive cloud-sync feature. Not packaged for Fedora yet, so it stays a # weak dependency rather than a hard one that would make the extension # uninstallable for everyone who does not use that feature. Recommends: python3-dvc %description SlicerOpenLIFU is a 3D Slicer extension for Openwater's OpenLIFU (Low Intensity Focused Ultrasound) research platform, providing an advanced interface to the features of the OpenLIFU app: a local treatment database, photogrammetry-based transducer localization for neuronavigation, sonication pre-planning with safety checks and hardware-configuration generation, and real-time device control of Openwater focused-ultrasound transducer hardware. The extension's additional Python dependencies (openlifu, bcrypt, threadpoolctl, requests, segno) are installed on first use from within Slicer (it prompts to pip-install them), so they are not packaged here. %prep %autosetup -p1 -n SlicerOpenLIFU-%{version} %build # Nothing to compile — pure scripted (Python) extension. %install python3 %{SOURCE1} \ "%{_builddir}/SlicerOpenLIFU-%{version}" \ "%{buildroot}" \ "%{slicer_inst}" \ "%{_builddir}/openlifu.files" \ "%{slicer_subdir}" %files -f %{_builddir}/openlifu.files %license LICENSE.txt %doc README.md %changelog * Sat Oct 10 2026 Morgan Hough - 1.21.1-4 - Patch1: OpenLIFUHome builds and cleans up without a main window (headless runs): no toolbar is created, and cleanup() removes only the toolbar setup() made instead of looking one up by name (RuntimeError at exit). Minimal backport of upstream ac878830. Found by the aarch64 sweep; reproduced and verified on f44 x86_64. * Sat Oct 10 2026 Morgan Hough - 1.21.1-3 - Patch0: QRegExpValidator(QRegExp) -> QRegularExpressionValidator(QRegularExpression) in OpenLIFUTransducerLocalization. Qt 6 removed both; setup() raised before creating algorithm_input_widget, and the half-built widget's observers then raised "no attribute 'algorithm_input_widget'" during later panel builds (8 modules blamed in the UI sweep). * Tue Sep 22 2026 Morgan Hough - 1.21.1-2 - Declare the Python modules the extension imports. The package built, installed, and passed repoclosure while being unusable: rpm generates dependencies from ELF linkage, and every one of these is a Python import, so nothing saw them. 26 shipped files import openlifu; the package required only 3dslicer, ctk-python and python3-vtkAddon. - The list is taken from upstream's own OpenLIFULib/dependency_utils.py rather than inferred: python_requirements_exist() demands bcrypt, requests, segno, threadpoolctl and xarray import, and find_spec locate openlifu, openlifu_sdk and openlifu_sdk.ui.simulated_interface. Failing it makes the extension offer to pip install into the user's Slicer -- the behaviour a distro package exists to prevent. - dvc stays a Recommends: its import is function-local in OpenLIFUHome._ensure_dvc_gdrive_support() and reached only when DVC_REPO_DIR is set, so requiring it would make the extension uninstallable for everyone not using Google Drive sync. - Needs python3-openlifu-sdk, added to this COPR in the same change. * Thu Jul 30 2026 Morgan Hough - 1.21.1-1 - Update to 1.21.1. IMPORTANT: 1.19.0 shipped a critical upstream bug in the time-averaged-intensity calculation that produced incorrect ISPTA -- a dose-safety figure in focused-ultrasound planning. Upstream fixed it in both v1.21.1 (mainline) and a v1.19.1 backport; take the mainline. 1.21.0 also re-introduced simulated hardware mode for testing the sonication control module without connected hardware, 1.20.0 added sample-database initialization, and 1.21.1 fixes Meshroom handling of whitespace in paths. Same 11-module layout, no packaging change needed beyond the version -- the slicer_subdir fix from -3 carries forward (verified: installer emits 65 files, all under Slicer-5.12). * Wed Jul 29 2026 Morgan Hough - Actually install into Slicer-5.12. The 1.19.0-2 retarget bumped the spec's %%global slicer_subdir but olifu-install-modules.py hardcoded "lib/Slicer-5.11/qt-scripted-modules" and never saw it, so the modules kept landing in a directory the 5.12 launcher does not scan -- every OpenLIFU module silently failed to register (the package installed clean and looked fine). Pass slicer_subdir into the installer instead of hardcoding it. Caught by the CDash extension-group-OpenLIFUHome check. * Wed Jul 22 2026 Morgan Hough - 1.19.0-2 - Retarget from Slicer-5.11 to Slicer-5.12 core (3dslicer 5.12.1); bump 3dslicer-devel floor to 5.12.1. * Sat Jun 13 2026 Morgan Hough - 1.19.0-1 - Initial package. SlicerOpenLIFU v1.19.0 (Openwater Low-Intensity Focused Ultrasound research platform) as a pure-scripted 3D Slicer extension: install the 10 OpenLIFU* scripted modules + the OpenLIFULib Python helper package into Slicer's qt-scripted-modules, replicating slicerMacroBuildScriptedModule without compiling or needing an install-tree SlicerConfig.cmake. Runtime Python deps are lazy pip-installed by the extension on first use.