# SlicerJupyter — Jupyter kernel that runs Slicer's Python interpreter # inside a notebook. Critical for educational / clinical-research use: # clinicians and educators can build reproducible notebooks that drive # Slicer's processing pipeline. %global commit d89a282d %global longcommit d89a282d54d7249fce07f62320779f82c5eb4604 %global slicer_inst %{_prefix}/lib/slicer %global slicer_subdir Slicer-5.12 %global snapdate 20260420 Name: 3dslicer-jupyter Version: 0 Release: 0.10.%{snapdate}git%{commit}%{?dist} Summary: Jupyter kernel for 3D Slicer (run Slicer Python from notebooks) License: BSD-3-Clause URL: https://github.com/Slicer/SlicerJupyter Source0: %{url}/archive/%{commit}/SlicerJupyter-%{commit}.tar.gz # The kernel embeds CPython via pybind11 but never linked libpython. Patch0: slicerjupyter-link-libpython.patch # With SUPERBUILD off, python_packages_DIR is unset and the install rule # degenerates to install(DIRECTORY "/") -- the whole filesystem root. Patch1: slicerjupyter-guard-python-packages.patch BuildRequires: cmake >= 3.20 BuildRequires: ninja-build BuildRequires: gcc-c++ BuildRequires: 3dslicer-devel >= 5.12.3 BuildRequires: qt6-qtbase-devel BuildRequires: zeromq-devel # Canonical Slicer-extension buildroot, grafted from 3dslicer-dmri.spec. # This spec predates the 5.12 retarget and was never built, so it carried # only a handful of BuildRequires -- find_package(Slicer) then failed inside # SlicerConfig.cmake:672 on find_package(RapidJSON), the first of ~38 missing. # Full Qt6 set find_package(Slicer)'s Qt check demands. BuildRequires: cmake(Qt6Core) BuildRequires: cmake(Qt6Gui) BuildRequires: cmake(Qt6Widgets) BuildRequires: cmake(Qt6Network) BuildRequires: cmake(Qt6Sql) BuildRequires: cmake(Qt6Svg) BuildRequires: cmake(Qt6Xml) BuildRequires: cmake(Qt6OpenGL) BuildRequires: cmake(Qt6OpenGLWidgets) BuildRequires: cmake(Qt6PrintSupport) BuildRequires: cmake(Qt6Multimedia) BuildRequires: cmake(Qt6MultimediaWidgets) BuildRequires: cmake(Qt6Core5Compat) BuildRequires: cmake(Qt6StateMachine) BuildRequires: cmake(Qt6WebEngineCore) BuildRequires: cmake(Qt6WebEngineWidgets) BuildRequires: cmake(Qt6WebChannel) BuildRequires: cmake(Qt6Qml) BuildRequires: cmake(Qt6Quick) BuildRequires: cmake(Qt6QuickWidgets) BuildRequires: cmake(Qt6Test) BuildRequires: cmake(Qt6UiTools) BuildRequires: qt6-qttools-devel BuildRequires: InsightToolkit5-devel >= 5.4.6-7 BuildRequires: InsightToolkit5-vtk-devel BuildRequires: vtk-devel >= 9.6 # Defines %%vtk_abi on Fedora's vtk (via vtk-abi-compat); our vtk provides it itself. BuildRequires: vtk(abi) BuildRequires: ctk-devel BuildRequires: qttesting-devel BuildRequires: vtkAddon-devel BuildRequires: qRestAPI-devel BuildRequires: python-pythonqt-devel BuildRequires: commontk-applauncher-devel BuildRequires: dcmtk-devel BuildRequires: jsoncpp-devel BuildRequires: libcurl-devel BuildRequires: python3-devel BuildRequires: rapidjson-devel BuildRequires: SlicerExecutionModel-devel BuildRequires: xeus-devel BuildRequires: xeus-python-devel BuildRequires: cppzmq-devel BuildRequires: xeus-zmq-devel BuildRequires: pybind11-devel BuildRequires: teem-devel # Carried over from main: these were on main's 0.5 and absent here. BuildRequires: commontk-applauncher-devel >= 0.1.34-0.9 BuildRequires: json-devel BuildRequires: libuuid-devel BuildRequires: openssl-devel BuildRequires: pybind11-json-devel Requires: 3dslicer >= 5.10.0 # Fedora has no python3-jupyter. The kernel is registered with # jupyter-kernelspec, from python3-jupyter-client; the jupyter command itself # comes from python3-jupyter-core. jupyter-client already depends on # jupyter-core, but name both so the reason for each is on the record. Requires: python3-jupyter-client Requires: python3-jupyter-core Requires: python3-zmq Requires: python3-tornado # VTK freezes its soname at libvtk*.so.1 across all of 9.x, so the auto-generated # libvtkCommonCore.so.1 requires are satisfied by 9.5, 9.6 and 9.7 alike: a consumer # built against one installs cleanly against another and fails only at load. # %%vtk_abi comes from vtk-devel's macros.vtk, so this re-pins on every rebuild. Requires: vtk(abi) = %{?vtk_abi}%{!?vtk_abi:UNSET} %description SlicerJupyter installs a Jupyter kernel that hosts 3D Slicer's embedded Python interpreter. This lets users build reproducible analysis notebooks that drive Slicer's MRML scene, modules, and visualization from a familiar notebook UI — without needing the Slicer GUI to be open. Critical for: * Educational use (workshops, online courses, NA-MIC training) * Clinical-research workflows that need reproducible scripted pipelines * Remote-collaboration scenarios where notebooks travel between sites * CI/CD of imaging analyses (test notebooks via papermill/nbval) %prep %autosetup -p1 -n SlicerJupyter-%{longcommit} # Interim: skip Slicer's extension CPack module. 3dslicer-devel is supposed to # ship extension_description.s4ext.in at lib/Utilities/Templates/Extensions/ # (3dslicer 5.12.3-4 step 5), but -4 packaged only the directories, so the # template is still missing and SlicerExtensionCPack still hard-fails. Drop # this sed once a 3dslicer that actually ships the file is in the repo. sed -i '/include(${Slicer_EXTENSION_CPACK})/d' CMakeLists.txt # Restore Qt plugin registration on qSlicerJupyterKernelModule. The header # declares its loadable-module IID inside "#ifdef Slicer_HAVE_QT5", and our # Slicer is Qt6 -- vtkSlicerConfigure.h ships "/* #undef Slicer_HAVE_QT5 */" -- # so Q_PLUGIN_METADATA compiles out, moc emits no plugin registration, and the # module ships with no qt_plugin_instance symbol. It builds, installs, and is # simply not loadable by Slicer, with nothing anywhere reporting a problem. # Verified on -0.9: 0 of 1. Same defect as 3dslicer-rt, where 15 of 16 modules # were affected; every other extension is clean. # # Removing the guard rather than defining Slicer_HAVE_QT5, which would assert # something false about the build. The header is CRLF, hence the \r? below. find . -name 'qSlicer*Module.h' -exec sed -i \ -e '/^#ifdef Slicer_HAVE_QT5\r\?$/{N;N;s/^#ifdef Slicer_HAVE_QT5\r\?\n\(.*Q_PLUGIN_METADATA.*\)\n#endif\r\?$/\1/}' {} + %build # vtk_abi comes from vtk-devel's macros.vtk. Absent means BuildRequires: vtk-devel is # missing, and the Requires above would ship as the vacuous "vtk(abi) = UNSET". test -n "%{?vtk_abi}" || { echo "ERROR: vtk_abi undefined - add 'BuildRequires: vtk-devel'" >&2; exit 1; } %cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING:BOOL=OFF \ -DSlicerJupyter_SUPERBUILD:BOOL=OFF \ `# Was %{_prefix}/lib/slicer-5.10.0/lib/cmake/Slicer -- a Slicer 5.10 layout` \ `# that has not existed since the 5.12 retarget. This spec was written then` \ `# and never built, so nothing ever caught it.` \ -DSlicer_DIR:PATH=%{slicer_inst}/lib/%{slicer_subdir} \ -DCMAKE_SKIP_INSTALL_RPATH:BOOL=ON \ -DCMAKE_INSTALL_PREFIX:PATH=%{slicer_inst} %cmake_build %install %cmake_install # Fail the build if the module plugin lost its Qt entry symbol. The %%prep sed # is the fix; this is what keeps it fixed. This bug's whole character is that it # produces a green build and a green install and no working module. bad=0; checked=0 for so in $(find %{buildroot} -path '*qt-loadable-modules*' -name 'libqSlicer*Module.so'); do case "$so" in *ModuleWidgets.so|*Plugins.so) continue;; esac checked=$((checked+1)) nm -D --defined-only "$so" 2>/dev/null | grep -q qt_plugin_instance || { echo "ERROR: $(basename $so) exports no qt_plugin_instance" >&2; bad=$((bad+1)); } done [ "$checked" -gt 0 ] || { echo "ERROR: found no loadable module plugins to check" >&2; exit 1; } [ "$bad" -eq 0 ] || { echo "ERROR: $bad of $checked module plugins are not loadable" >&2; exit 1; } echo "OK: all $checked loadable module plugins export qt_plugin_instance" %files %license LICENSE %doc README.md %{slicer_inst}/lib/%{slicer_subdir}/qt-loadable-modules/*Jupyter* %{slicer_inst}/lib/%{slicer_subdir}/qt-scripted-modules/*Jupyter* %{slicer_inst}/share/%{slicer_subdir}/qt-loadable-modules/*Jupyter* %changelog * Tue Sep 15 2026 Morgan Hough - 0-0.10.20260420gitd89a282d - Restore Qt plugin registration on qSlicerJupyterKernelModule, which has been shipping unloadable. Its Q_PLUGIN_METADATA sits inside "#ifdef Slicer_HAVE_QT5"; our Slicer is Qt6 and vtkSlicerConfigure.h ships "/* #undef Slicer_HAVE_QT5 */", so moc emitted no plugin registration and the module shipped with no qt_plugin_instance symbol. It built, installed, and was simply never loadable -- with nothing reporting a failure. - Same defect as 3dslicer-rt (15 of 16 modules). A sweep of every extension found only these two; core 3dslicer is 23 of 23 clean. - %%install now fails the build if the module plugin lacks the entry symbol, or if no module plugins are found to check. * Fri Sep 04 2026 Morgan Hough - 0-0.8.20260420gitd89a282d - Fix an unsatisfiable Requires. The package asked for python3-jupyter, which does not exist in Fedora and is provided by nothing in either COPR, so 3dslicer-jupyter built green but could never be installed. Require python3-jupyter-client (ships jupyter-kernelspec, which registers the kernel) and python3-jupyter-core (ships the jupyter command). * Wed Sep 02 2026 Morgan Hough - 0-0.7.20260420gitd89a282d - Fix the %%license line. It read '%%license License.txt 2>/dev/null || true'; %%files takes filenames, not shell, so rpm looked for files named '2>/dev/null', '||' and 'true' and failed with 'File not found: .../licenses/3dslicer-jupyter/true'. SlicerJupyter ships LICENSE, not License.txt. * Wed Sep 02 2026 Morgan Hough - 0-0.6.20260420gitd89a282d - Add slicerjupyter-guard-python-packages.patch. With SUPERBUILD off, python_packages_DIR is never set, so the top-level install(DIRECTORY "${python_packages_DIR}/") became install(DIRECTORY "/") and CMake tried to copy the entire filesystem root into the buildroot, failing on ///dev/mapper/control. Guard both install() calls on the variable being set. - Correct a bogus changelog weekday in the previous entry. * Tue Sep 01 2026 Morgan Hough - 0-0.5.20260420gitd89a282d - Add slicerjupyter-link-libpython.patch. With SUPERBUILD off the kernel module links only xeus, xeus-python and cppzmq, but it embeds CPython through pybind11's headers, so the link failed on PyObject_Str, PyProperty_Type and PyThread_tss_delete. In a SuperBuild that linkage came in transitively with the ExternalProject-built pybind11. Find Python3's Development component and add Python3::Python to MODULE_TARGET_LIBRARIES. The same patch adds PythonQt: the module drives Slicer's Python console through it and hit the same gap (PythonQt::init, PythonQtObjectPtr::evalScript), so python-pythonqt-devel joins the BuildRequires. * Sun Aug 23 2026 Morgan Hough - 0-0.4.20260420gitd89a282d - Rebuild against VTK 9.7.0. VTK here installs unversioned sonames (libvtk*.so.1), so 9.7 replaces 9.6 in place and every consumer must be rebuilt or it fails at runtime with undefined symbols. - Complete the grafted Slicer-extension buildroot. find_package(Slicer) died in SlicerConfig.cmake:672 on find_package(RapidJSON): the graft from 3dslicer-dmri.spec had left out rapidjson-devel, jsoncpp-devel, libcurl-devel, python3-devel and SlicerExecutionModel-devel. Unrelated to VTK 9.7 -- this spec had simply never been built with the full set. - Set _SUPERBUILD=OFF and correct CMAKE_INSTALL_PREFIX. The extension CMakeLists default SUPERBUILD to ON, which makes the configure run ExternalProject_Add and try to git-clone its dependencies at build time -- impossible in a COPR/mock build, which has neither network nor git (Git_VERSION=''). The dependencies are already packaged, so the inner build is the right one. The install prefix AND the %files paths still pointed at the Slicer 5.10.0 layout that has not existed since the 5.12 retarget; both now use %%{slicer_inst}/%%{slicer_subdir}. - Drop include(${Slicer_EXTENSION_CPACK}) in %prep. It generates Extension-Manager packaging only, and against an installed Slicer it fails: the README/LICENSE vars it demands have no defaults, and the .s4ext template it configures is looked for under lib/Utilities/Templates while 3dslicer installs it under share/Slicer-5.12/Wizard/Templates. No install() rules are lost. - Drop the local workaround that deleted include(${Slicer_EXTENSION_CPACK}): 3dslicer 5.12.3-4 now installs extension_description.s4ext.in where the installed SlicerConfig looks for it, so the module works as intended and every extension benefits rather than each one patching around it. * Sun Aug 02 2026 Morgan Hough - 0-0.3.20260420gitd89a282d - Graft the canonical Slicer-extension buildroot (~34 BuildRequires) from 3dslicer-dmri.spec. This spec predates the 5.12 retarget and was never built, so it carried only a handful; find_package(Slicer) then failed inside SlicerConfig.cmake:672 on find_package(RapidJSON) -- the first of the missing set, which also covers the full Qt6 component list, ITK/VTK/CTK/DCMTK devel, SlicerExecutionModel, PythonQt, qRestAPI, teem and vtkAddon. * Sun Aug 02 2026 Morgan Hough - 0-0.2.20260420gitd89a282d - Point Slicer_DIR at the 5.12 layout: it was %%{_prefix}/lib/slicer-5.10.0/lib/ cmake/Slicer, a path that has not existed since the 5.12 retarget. The spec was written then and never built, so nothing caught it. * Wed May 27 2026 Morgan Hough - 0-0.1.20260420gitd89a282d - Initial COPR package tracking SlicerJupyter master. - Jupyter kernel runs Slicer's embedded Python; pairs with Fedora's python3-jupyter / Jupyter Lab on the user side.