# PlusLib -- the Plus Toolkit's data acquisition library: the layer that turns # a tracker or an imaging device into a stream of calibrated, timestamped # transforms and frames, and serves them over OpenIGTLink. # # This is the missing link in the open neuronavigation stack. NaviNIBS ships # PlusServer configuration files for the NDI Polaris Vega and talks to it over # OpenIGTLink; 3D Slicer's SlicerIGT does the same. Without Plus, NaviNIBS on # Fedora runs only with its simulated-tools addon and no real hardware. # See docs/open-neuronavigation.md and docs/neuronavigation-hardware.md. # # Chain: ndicapi + aruco -> PlusLib -> PlusApp (PlusServer) -> NaviNIBS / SlicerIGT # # Superbuild OFF, everything from system packages. Upstream's normal build is # the PlusBuild superbuild, which clones and builds VTK, ITK, OpenIGTLink, # OpenIGTLinkIO, IGSIO, ndicapi and aruco itself. We have all seven packaged, # so this builds against them -- the same rule applied to igsio and Pulse. # # DEVICE BACKENDS: only the ones buildable from free software are enabled. # Roughly sixty exist; most are ultrasound and most need a vendor SDK under # NDA. Enabled here: NDI Polaris/Aurora (ndicapi), optical marker tracking # (aruco, any camera), Intel RealSense, V4L2, OpenCV capture, OpenVR/SteamVR # (Vive trackers), OpenIGTLink, and the simulated/replay sources that make # hardware-free testing possible. %global commit c796218916c2854c3fd8d2286facac8a5821ceb5 %global shortcommit %(c=%{commit}; echo ${c:0:8}) %global snapdate 20260820 Name: PlusLib Version: 2.9.0 Release: 0.1.%{snapdate}git%{shortcommit}%{?dist} Summary: Data acquisition and calibration library for image-guided intervention # LICENCE, and this one needs a human decision recorded rather than a guess. # # License.txt is the "Plus Contribution and Software License Agreement" v1.0, # which the file itself calls "a BSD style license, with extensions to cover # contributions and other issues specific to Plus". Part B is the grant we # rely on and it is permissive: # # B.1 "a royalty-free, non-exclusive license to use, reproduce, make # derivative works of, display and distribute the Software" # B.2 "includes without limitation the right to (i) incorporate the # Software into proprietary programs" # # with BSD-shaped conditions: carry the licence text along (B.1.b), preserve # attributions (B.1.c), mark modified versions (B.1.d), and an explicitly # non-binding request to publish modifications (B.1.e, "under no obligation"). # # The clause to think about is B.4: "The Software has been designed for # research purposes only and has not been reviewed or approved by the Food and # Drug Administration ... CLINICAL APPLICATIONS ARE NEITHER RECOMMENDED NOR # ADVISED." # # We read that as a DISCLAIMER, not a field-of-use restriction, and therefore # free. Three reasons: it describes what the software was designed for rather # than limiting the grant; the grant in B.1/B.2 has no field limitation and # expressly permits proprietary use; and B.4 itself goes on to contemplate # commercialisation ("Any commercialization of the Software is at the sole # risk of the party ... engaged in such commercialization"), which a # non-commercial restriction would forbid outright. # # Contrast mni_autoreg, which we put in the nonfree repository: its notice # says "This package is provided for research use only" as a flat condition on # use. That is a restriction. This is an FDA non-approval notice of the same # kind ITK, DCMTK and 3D Slicer carry, and Fedora ships all three. # # No SPDX identifier exists for it and it is not in fedora-license-data, so it # is expressed as a LicenseRef. **Morgan should confirm this reading before the # package is promoted anywhere that matters.** License: LicenseRef-Plus-Toolkit-BSD-style URL: https://github.com/PlusToolkit/PlusLib Source0: %{url}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz # Two dependency-location problems. # # VTK and ITK are found with # FIND_PACKAGE(VTK NO_MODULE REQUIRED PATHS ${VTK_DIR} NO_DEFAULT_PATH) # FIND_PACKAGE(ITK REQUIRED PATHS ${ITK_DIR} NO_DEFAULT_PATH) # The PATHS + NO_DEFAULT_PATH form skips the standard config search that # modern VTK/ITK config packages rely on, so Fedora's are not found even with # VTK_DIR/ITK_DIR set. Exactly the same patch igsio needed. # # And PLUS_USE_V4L2 does FIND_PACKAGE(V4L2 REQUIRED), but there is no # FindV4L2.cmake in the tree and V4L2 ships no CMake config, so that option # cannot succeed for anyone -- it is unusable as shipped. The backend really # needs and libv4l2, so locate those with # CHECK_INCLUDE_FILE and FIND_LIBRARY instead. Patch0: PlusLib-find-system-deps.patch BuildRequires: cmake >= 3.16 BuildRequires: gcc-c++ BuildRequires: ninja-build BuildRequires: vtk-devel BuildRequires: InsightToolkit5-devel BuildRequires: igsio-devel # igsio's installed headers include from vtkAddon, # and IGSIOConfig.cmake does not carry that include directory, so name it here # and add the path in %%build. igsio-devel gained the matching Requires in # 1.0-0.9, but being explicit is right for a build dependency. BuildRequires: vtkAddon-devel BuildRequires: openigtlink-devel BuildRequires: openigtlinkio-devel BuildRequires: ndicapi-devel BuildRequires: aruco-devel BuildRequires: opencv-devel BuildRequires: librealsense-devel BuildRequires: openvr-devel BuildRequires: zlib-devel # V4L2 video capture: the header comes from kernel-headers, the library from # libv4l-devel. See Patch0 -- upstream's FIND_PACKAGE(V4L2) cannot work. BuildRequires: libv4l-devel BuildRequires: kernel-headers # OpenVR/SteamVR: Fedora ships only openvr.pc, no CMake config, so Patch0 # falls back to pkg-config. BuildRequires: pkgconfig(openvr) %description PlusLib is the data acquisition, pre-processing and calibration library of the Plus Toolkit (Public software Library for UltraSound). It provides a uniform interface over tracking and imaging hardware: devices are described in an XML configuration, their streams are timestamped and temporally aligned, spatial calibrations are applied, and the result is published over OpenIGTLink for consumers such as 3D Slicer, SlicerIGT and NaviNIBS. This build enables the device backends that need no proprietary SDK: * NDI Polaris and Aurora trackers, via ndicapi * optical marker tracking with any camera, via aruco * Intel RealSense depth cameras * Video4Linux2 and OpenCV video capture * OpenVR / SteamVR devices, including Vive trackers * OpenIGTLink client and server devices * replay from recorded sequences and simulated tools, for testing without hardware %package devel Summary: Development files for %{name} Requires: %{name}%{?_isa} = %{version}-%{release} Requires: igsio-devel%{?_isa} Requires: openigtlink-devel%{?_isa} Requires: vtk-devel%{?_isa} %description devel Headers and CMake package configuration for building against %{name}. %prep %autosetup -p1 -n %{name}-%{commit} %build # IGSIOConfig.cmake does not export vtkAddon's include directory, but igsio's # own installed headers include from it. # igsio's installed headers include from vtkAddon, # but IGSIOConfig.cmake does not export that include directory. (The # librealsense2 include path is handled properly by Patch0 instead.) export CXXFLAGS="%{build_cxxflags} -I%{_includedir}/vtkAddon" # Rendering and the Qt widgets are PlusApp's business, not the library's. # ITK_NO_IO_FACTORY_REGISTER_MANAGER: ITK's register manager generates a list # of every ImageIO factory to register at static-init time, and Fedora's ITK # config lists optional modules whose libraries it does not ship -- so the # generated code references itk::PhilipsRECImageIOFactoryRegister__Private and # itk::ScancoImageIOFactoryRegister__Private, which nothing defines. (Same # class of mismatch as the FFTW factory symbol that makes python3-simpleitk # unimportable; see docs/COORDINATION.md.) PlusLib handles the manager being # off explicitly, and ITK still finds its installed IO libraries through the # ordinary runtime factory lookup. # # CMAKE_BUILD_TYPE must be set explicitly. PlusLibInstallLibrary() installs # with `CONFIGURATIONS ${CONFIG}`, where CONFIG falls back to "Release" when # neither CMAKE_CONFIGURATION_TYPES nor CMAKE_BUILD_TYPE is set -- and Fedora's # %%cmake sets neither. The configuration name then never matches the build, so # every INSTALL(TARGETS) is skipped and the headers install without a single # library. RelWithDebInfo keeps debuginfo and makes the rules fire. %cmake -G Ninja \ -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo \ -DITK_NO_IO_FACTORY_REGISTER_MANAGER:BOOL=ON \ -DBUILD_SHARED_LIBS:BOOL=ON \ -DBUILD_TESTING:BOOL=OFF \ -DBUILD_DOCUMENTATION:BOOL=OFF \ -DPLUS_BUILD_WIDGETS:BOOL=OFF \ -DPLUS_USE_INTEL_MKL:BOOL=OFF \ -DPLUS_TEST_HIGH_ACCURACY_TIMING:BOOL=OFF \ -DVTK_DIR:PATH=%{_libdir}/cmake/vtk \ -DITK_DIR:PATH=%{_prefix}/lib/cmake/ITK-5.4 \ -DPLUS_USE_OpenIGTLink:BOOL=ON \ -DPLUS_USE_NDI:BOOL=ON \ -DPLUS_USE_POLARIS:BOOL=ON \ -DPLUS_USE_aruco:BOOL=ON \ -DPLUS_USE_OPTICAL_MARKER_TRACKER:BOOL=ON \ -DPLUS_USE_OpenCV:BOOL=ON \ -DPLUS_USE_INTELREALSENSE:BOOL=ON \ -DPLUS_USE_V4L2:BOOL=ON \ -DPLUS_USE_STEAMVR:BOOL=ON \ -Daruco_DIR:PATH=%{_libdir}/cmake/aruco \ -Dndicapi_DIR:PATH=%{_libdir}/cmake/ndicapi \ -DIGSIO_DIR:PATH=%{_libdir}/cmake/IGSIO \ -DOpenIGTLink_DIR:PATH=%{_libdir}/cmake/OpenIGTLink \ -DOpenIGTLinkIO_DIR:PATH=%{_libdir}/cmake/igtlio \ -DPLUSLIB_LIBRARY_INSTALL:STRING=%{_lib} \ -DPLUS_INSTALL_EXTERNAL_LIBRARIES:BOOL=OFF \ -DCMAKE_SKIP_INSTALL_RPATH:BOOL=ON %cmake_build %install %cmake_install # PlusLib's bundled xio helper installs a static library (and to /usr/lib, via # a PLUSLIB_ARCHIVE_INSTALL that is hardcoded like the others). Its objects are # already inside the shared libraries that use it, and Fedora does not ship # static libraries without a reason, so drop it. rm -f %{buildroot}%{_prefix}/lib/libvtkxio.a %{buildroot}%{_libdir}/libvtkxio.a # Nothing else should have landed in a bare /usr/lib on a 64-bit build. if [ -d %{buildroot}%{_prefix}/lib ]; then rmdir --ignore-fail-on-non-empty %{buildroot}%{_prefix}/lib if [ -d %{buildroot}%{_prefix}/lib ]; then echo "unexpected files left in /usr/lib:" find %{buildroot}%{_prefix}/lib | head exit 1 fi fi %check # No hardware in a build root. What can be asserted is that the libraries # resolve, that the device backends we asked for were actually compiled in # (a CMake option can be silently ignored, which is how a build looks green # while supporting nothing), and that the configuration headers agree. # ldd -r must be able to find the SIBLING libraries, which are still only in # the buildroot -- without LD_LIBRARY_PATH every cross-library symbol looks # undefined and this check fails on a correct package. (The libraries do record # each other: objdump -p shows NEEDED libvtkPlusCommon.so.) export LD_LIBRARY_PATH=%{buildroot}%{_libdir} nlibs=0 for so in %{buildroot}%{_libdir}/libvtkPlus*.so; do [ -e "$so" ] || continue nlibs=$((nlibs+1)) # VTK's module autoinit emits references to *_AutoInit_Construct for # optional modules (vtkRenderingVRModels, vtkFiltersParallelDIY2, ...). # By VTK's design those are satisfied when the final consumer links the # module -- Fedora's VTK does provide both libraries -- so an intermediate # library legitimately carries them unresolved. Everything else must # resolve. bad=$(ldd -r "$so" 2>/dev/null | grep '^undefined symbol' \ | grep -v '_AutoInit_Construct' || :) if [ -n "$bad" ]; then echo "undefined symbols in $so:"; echo "$bad"; exit 1 fi done # A loop that iterated nothing would pass silently, which is how a check stops # being a check. test "$nlibs" -ge 8 || { echo "only $nlibs Plus libraries found, expected 8+"; exit 1; } echo "checked $nlibs Plus libraries for undefined symbols" cfg=%{buildroot}%{_includedir}/PlusConfigure.h test -f "$cfg" || cfg=$(find %{buildroot} -name PlusConfigure.h | head -1) test -n "$cfg" -a -f "$cfg" || { echo "PlusConfigure.h not installed"; exit 1; } # Each backend we enabled must appear as a #define, not merely as a cache # variable we passed in. for want in PLUS_USE_OpenIGTLink PLUS_USE_NDI PLUS_USE_OPTICAL_MARKER_TRACKER \ PLUS_USE_INTELREALSENSE PLUS_USE_V4L2; do grep -qE "^#define +$want" "$cfg" \ || { echo "backend not compiled in: $want"; grep -c . "$cfg"; exit 1; } done # And the tracker backends must really be linked against their drivers, # otherwise the device exists in name only. if [ -e %{buildroot}%{_libdir}/libvtkPlusDataCollection.so ]; then objdump -p %{buildroot}%{_libdir}/libvtkPlusDataCollection.so \ | grep -E 'NEEDED.*(ndicapi|aruco)' \ || { echo "DataCollection links neither ndicapi nor aruco"; exit 1; } fi echo "PlusLib: libraries resolve and the free device backends are compiled in" %files %license License.txt %doc README.md %{_libdir}/libvtkPlus*.so %{_datadir}/PlusLib-2.9/ # Ransac model-fitting helpers, the only executables this library installs. %{_bindir}/planeEstimation %{_bindir}/sphereEstimation %files devel %{_includedir}/PlusLib-2.9/ %{_libdir}/cmake/PlusLib-2.9/ %changelog * Sat Sep 26 2026 Morgan Hough - 2.9.0-0.1.20260820gitc7962189 - Initial package: the hardware layer for the open neuronavigation stack. Superbuild off, built against system VTK, ITK, IGSIO, OpenIGTLink, OpenIGTLinkIO, ndicapi and aruco. Only the device backends that need no proprietary SDK are enabled. - rpmlint reports invalid-soname for each library: PlusLib versions none of them (libvtkPlusCommon.so with no .so.N), which is upstream's choice and matches how igsio ships. Not worth patching a soname scheme onto a library set whose consumers are all built alongside it. - Licence expressed as a LicenseRef with the reasoning recorded in the spec: the "research purposes only" wording is in the FDA disclaimer, not the grant, and the grant expressly permits proprietary use and contemplates commercialisation. Needs Morgan's confirmation.