%global commit 69f80b6e676ce56d1d99825c3579e59fc1e7e419
%global shortcommit %(c=%{commit}; echo ${c:0:7})
# FPC writes no DWARF that debugedit can use (Debian: "Lazarus code does not
# export debug symbols"), and lazbuild strips the binary itself.
%define debug_package %{nil}
Name: surfice
# Upstream's own version string (vers.inc: kVers='1.0.20260427'). The last
# tag, v1.0.20211006, predates the Python bridge rework and TRX support.
Version: 1.0.20260427
Release: 1%{?dist}
Summary: Surface viewer for brain meshes, tractography and connectomes
# Code: BSD-2-Clause (Chris Rorden; PythonBridge, Ryan Joseph);
# MIT: meshify_simplify_quadric.pas (Fast-Quadric-Mesh-Simplification);
# MPL-1.1: track_simplify.pas (Nils Haeck's Douglas-Peucker);
# Zlib: ctm_loader.pas (OpenCTM MG2, Marcus Geelnard).
# lzma/ is a Pascal port of the public-domain LZMA SDK decoder with no notice.
# SynZip.pas (MPL-1.1 OR GPL OR LGPL) is only used with FASTGZ, which is off,
# and is deleted in %%prep with the prebuilt CloudFlare zlib objects it links.
License: BSD-2-Clause AND MIT AND MPL-1.1 AND Zlib
URL: https://github.com/neurolabusc/surf-ice
Source0: %{url}/archive/%{commit}/surf-ice-%{shortcommit}.tar.gz
Source1: surfice.metainfo.xml
# Debian's -dPYTHON_DYNAMIC build, made to work here: no static
# libpython3.7m.a on Linux, and find /usr/lib64/libpython3.X.so.1.0.
Patch0: surfice-python-dynamic-linux.patch
# The dynamic import table names seven functions removed since Python 3.10;
# the first failed Assert stopped Python from loading at all on 3.14.
Patch1: surfice-python-removed-api.patch
BuildRequires: lazarus >= 4.0
BuildRequires: fpc
BuildRequires: fpc-src
BuildRequires: lazarus-lcl-qt5
BuildRequires: qt5pas-devel
BuildRequires: desktop-file-utils
BuildRequires: /usr/bin/appstream-util
# %%check: render a mesh through the embedded Python under Xvfb, llvmpipe
BuildRequires: python3-libs
BuildRequires: python3-pillow
BuildRequires: xorg-x11-server-Xvfb
BuildRequires: /usr/bin/xvfb-run
BuildRequires: mesa-dri-drivers
Requires: %{name}-data = %{version}-%{release}
Requires: qt5pas%{?_isa}
# Python scripting dlopen()s libpython3.X.so.1.0; nothing records that.
Requires: python3-libs%{?_isa}
Requires: hicolor-icon-theme
Recommends: pigz
%description
Surf Ice renders neuroimaging surface meshes with statistical overlays,
fibre tractography and connectome node and edge graphs, with ambient
occlusion, matcap shading, clipping planes and transparency. It reads
GIfTI, FreeSurfer, MZ3, OBJ, PLY, STL, VTK, CIFTI and OpenCTM meshes,
TrackVis, TRX and VTK streamlines and BrainNet node/edge files, and can
turn a NIfTI volume into a mesh. Rendering can be scripted in Python
(import gl) for batch figure production. It is the surface companion to
MRIcroGL.
%package data
Summary: Shaders, colour tables, matcaps and sample meshes for Surf Ice
BuildArch: noarch
# BSD-2-Clause: shaders, colour tables, scripts, sample fMRI meshes;
# GPL-2.0-or-later: the Blender matcaps (Resources/matcap/license.txt);
# MIT: the three.js Porcelain matcap;
# HPND: meshes from the MNI ICBM 2009 template (sample/mni152_2009*);
# GPL-1.0-or-later: BrainNet/ meshes, from BrainNet Viewer ("GNU GPL", no
# version stated);
# OFL-1.1, Apache-2.0: signed-distance atlases rendered from Montserrat,
# Open Sans, Oswald, Oxygen (OFL) and Roboto (Apache, the default).
# Removed in %%prep, not shipped: atlas/AICHAhr.lh.mz3 (AICHA: "freely use
# it for non profit research purposes", any other use by permission) and
# Resources/matcap/Fubax.jpg (from fubax-shaders, commercial use by
# negotiation); the Ubuntu font atlas (Ubuntu Font Licence, not allowed in
# Fedora).
License: BSD-2-Clause AND GPL-2.0-or-later AND MIT AND HPND AND GPL-1.0-or-later AND OFL-1.1 AND Apache-2.0
%description data
Architecture-independent files for Surf Ice: GLSL shaders, colour lookup
tables, matcap textures, font atlases, example Python scripts, and sample
meshes (MNI152 2009 cortex, BrainNet Viewer meshes and node/edge files,
and an fMRI motor activation).
%prep
%autosetup -n surf-ice-%{commit} -p1
# Prebuilt binaries: static libpython 3.7, CloudFlare zlib objects for three
# platforms, a Windows Python and runtime, and the macOS app bundle.
rm -rf PythonBridge/x86_64-linux PythonBridge/x86_64-darwin PythonBridge/aarch64-darwin \
x86_64-linux x86_64-darwin x86_64-win64 surfice.app
rm -f Resources/python36.dll Resources/python36.zip Resources/vcruntime140.dll
rm -f SynZip.pas SynFPCLinux.pas Synopse.inc
# Non-free data (see the -data License comment).
rm -f atlas/AICHAhr.lh.mz3 Resources/matcap/Fubax.jpg Resources/lut/Ubuntu.json Resources/lut/Ubuntu.png
rmdir atlas
# Upstream commits most files 0755.
find . -type f \( -name '*.txt' -o -name '*.md' -o -name '*.py' -o -name '*.mz3' -o -name LICENSE \) -exec chmod 0644 {} +
find . \( -name '*.bat' -o -name '*.command' -o -name '*.dll' -o -name '*.so' \
-o -name '*.a' -o -name '*.o' -o -name '*.ppu' \) -print -delete
# surfice.lpi targets macOS on Apple Silicon: TargetCPU aarch64 would
# cross-compile on x86_64, and -k-ld_classic is an Apple linker flag.
# Build for the host, link libpython at run time, PIC code as in mricrogl.
sed -i -e '//d' \
-e '/|-dPYTHON_DYNAMIC -Cg -k-no-pie"/>|' \
surfice.lpi
grep -q -- '-dPYTHON_DYNAMIC' surfice.lpi
! grep -q 'TargetCPU Value="aarch64"' surfice.lpi
%build
lazbuild --lazarusdir=%{_libdir}/lazarus --ws=qt5 --build-all surfice.lpi
test -x surfice
%install
# The binary finds its data next to itself: Resources/ (ResourceDir) and
# sample/, BrainNet/ (default meshes, from the executable's directory), so it
# lives in libexec beside links into /usr/share, as mricrogl does.
install -Dpm 0755 surfice %{buildroot}%{_libexecdir}/%{name}/%{name}
mkdir -p %{buildroot}%{_datadir}/%{name}
cp -pr Resources/lut Resources/matcap Resources/script Resources/shaders \
Resources/shadersOld %{buildroot}%{_datadir}/%{name}/
# Each folder keeps its Reference.txt (the provenance of its meshes).
cp -pr sample BrainNet %{buildroot}%{_datadir}/%{name}/
find %{buildroot}%{_datadir}/%{name} -type f -exec chmod 0644 {} +
ln -s ../../share/%{name} %{buildroot}%{_libexecdir}/%{name}/Resources
ln -s ../../share/%{name}/sample %{buildroot}%{_libexecdir}/%{name}/sample
ln -s ../../share/%{name}/BrainNet %{buildroot}%{_libexecdir}/%{name}/BrainNet
mkdir -p %{buildroot}%{_bindir}
cat > %{buildroot}%{_bindir}/%{name} <<'EOF'
#!/bin/sh
exec %{_libexecdir}/%{name}/%{name} "$@"
EOF
chmod 0755 %{buildroot}%{_bindir}/%{name}
install -Dpm 0644 deb/surfice.1 %{buildroot}%{_mandir}/man1/surfice.1
install -Dpm 0644 surfice.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/%{name}.svg
install -Dpm 0644 icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/%{name}.png
# Upstream's (Debian's) entry, with an icon name instead of an absolute path,
# no GTK category (this is a Qt build), and one main category.
desktop-file-install --dir=%{buildroot}%{_datadir}/applications \
--set-icon=%{name} \
--set-key=Categories --set-value="Science;MedicalSoftware;DataVisualization;" \
--set-key=Keywords --set-value="MRI;mesh;GIfTI;tractography;connectome;brain;neuroimaging;" \
deb/surfice.desktop
install -Dpm 0644 %{SOURCE1} %{buildroot}%{_metainfodir}/%{name}.metainfo.xml
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/%{name}.metainfo.xml
%check
desktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop
# Render the shipped MNI152 cortex, then the same view with the shipped
# motor activation, through the embedded Python from the installed tree. A
# PNG exists only if libpython was found and loaded and the gl module ran;
# the comparisons fail if the mesh did not load or the overlay was not drawn.
# Preferences for a headless run: Xvfb's llvmpipe offers no multisampled GLX
# configs, so the default MultiSample=1 makes Lazarus raise "Could not find
# FB config" in a modal dialog. On Mesa, Surf Ice drops to "Poor" quality,
# where savebmpxy reads back whatever the window last painted (stale under
# a script); "Better" (MesaWarned=1, RenderQuality=1) renders each capture
# into a framebuffer object. The -S script also starts before the GL control
# is realised, hence the first wait.
for d in Resources/shaders Resources/matcap sample BrainNet; do
test -d %{buildroot}%{_libexecdir}/%{name}/$d
done
export HOME=$PWD/check-home LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe
mkdir -p "$HOME/.surfice"
printf '[BOOL]\nMultiSample=0\nMesaWarned=1\n[INT]\nRenderQuality=1\n' > "$HOME/.surfice/surfice.ini"
S=%{buildroot}%{_libexecdir}/%{name}/sample
cat > check.py < 90
over_px = sum(diff(a, b) for a, b in zip(mesh, over))
print(f'background={bg} mesh={fg} overlay={over_px}')
assert sum(p != bg for p in empty) < 0.02 * len(empty), 'empty scene not empty'
assert fg > 0.20 * len(mesh), 'mesh not rendered'
assert 200 < over_px < 0.10 * len(mesh), 'overlay not rendered'
EOF
%files
%license LICENSE
%doc README.md COMMANDS.md PYTHON.md
%{_bindir}/%{name}
%{_libexecdir}/%{name}/
%{_mandir}/man1/surfice.1*
%{_datadir}/applications/%{name}.desktop
%{_datadir}/icons/hicolor/scalable/apps/%{name}.svg
%{_datadir}/icons/hicolor/256x256/apps/%{name}.png
%{_metainfodir}/%{name}.metainfo.xml
%files data
%license LICENSE
%{_datadir}/%{name}/
%changelog
* Fri Oct 16 2026 Morgan Hough - 1.0.20260427-1
- Initial package: master 69f80b6, Qt 6 widgetset, Python scripting through
the system libpython loaded at run time.
- Prebuilt libpython, zlib objects and Windows runtimes removed; the AICHA
atlas (non-commercial), the Fubax matcap and the Ubuntu font atlas
removed.
- %%check renders a shipped mesh and overlay through a Python script under
Xvfb with llvmpipe and checks the PNG.