# ShapeWorks is statically linked (huge unstripped binaries); debuginfo is not useful %global debug_package %{nil} # Bundled / vendored components (pinned commits) %global gc_commit 8b20898f6c7be1eab827a9f720c8fd45e58ae63c %global gc_short 8b20898 %global happly_commit 88f79725d32d78e0e637ebf42d8432d519e1c7d0 %global happly_short 88f7972 %global libigl_version 2.3.0 # Eigen: ITK's public headers include Eigen in every ITK-including translation # unit, so ShapeWorks must compile against the same Eigen as ITK. Since # InsightToolkit5 5.4.7-4 that is the system eigen3 (5.0.x), not ITK's bundled # itkeigen, which is no longer installed. Name: shapeworks Version: 6.7.0 Release: 7%{?dist} Summary: Statistical shape modeling: particle-based correspondence and PCA # ShapeWorks core: MIT. Bundled: geometry-central (MIT), happly (MIT), # libigl core (MPL-2.0) + its copyleft helpers (GPL-3.0-or-later), and in-tree # ExternalLibs (trimesh2, tinyxml, robin_hood, optparse, # ordered_map - BSD/MIT/Zlib). License: MIT AND MPL-2.0 AND GPL-3.0-or-later AND BSD-3-Clause AND Zlib URL: https://shapeworks.sci.utah.edu/ Source0: https://github.com/SCIInstitute/ShapeWorks/archive/v%{version}/ShapeWorks-%{version}.tar.gz # geometry-central pinned commit (ShapeWorks' FindGEOMETRYCENTRAL expects this old API) Source1: https://github.com/nmwsharp/geometry-central/archive/%{gc_commit}/geometry-central-%{gc_short}.tar.gz # happly is a geometry-central git submodule, absent from the archive Source2: https://github.com/nmwsharp/happly/archive/%{happly_commit}/happly-%{happly_short}.tar.gz # libigl 2.3.0: bundled full source tree (ShapeWorks' FindLIBIGL needs its cmake module) Source4: https://github.com/libigl/libigl/archive/refs/tags/v%{libigl_version}/libigl-%{libigl_version}.tar.gz # Use system libraries; disable Studio GUI (needs out-of-tree patched JKQTPlotter # Qt5 fork); relax the Python 3.12 EXACT pin and move it after project(); skip the # git-lfs sentinel; add geometry-central include dir globally. See patch comments. Patch0: shapeworks-cmake-system-libs.patch BuildRequires: gcc-c++ BuildRequires: cmake BuildRequires: ninja-build BuildRequires: make BuildRequires: chrpath BuildRequires: python3-devel BuildRequires: pybind11-devel # Core ShapeWorks dependencies BuildRequires: InsightToolkit5-devel # header-only; -static per Fedora guidelines for tracking header-only use BuildRequires: eigen3-static BuildRequires: InsightToolkit5-vtk-devel BuildRequires: vtk-devel BuildRequires: openvdb-devel BuildRequires: blosc-devel BuildRequires: imath-devel BuildRequires: tbb-devel BuildRequires: acvd-devel BuildRequires: xlnt-devel BuildRequires: boost-devel BuildRequires: json-devel BuildRequires: spdlog-devel BuildRequires: qt5-qtbase-devel BuildRequires: qt5-qtsvg-devel BuildRequires: libomp-devel BuildRequires: libminc-devel # VTK find_package() transitively resolves these at configure time BuildRequires: freetype-devel BuildRequires: pugixml-devel BuildRequires: fmt-devel BuildRequires: utf8cpp-devel BuildRequires: PEGTL-devel BuildRequires: jsoncpp-devel BuildRequires: hdf5-devel BuildRequires: double-conversion-devel BuildRequires: expat-devel BuildRequires: glew-devel BuildRequires: lz4-devel BuildRequires: libxml2-devel BuildRequires: libtiff-devel BuildRequires: libjpeg-turbo-devel BuildRequires: libpng-devel BuildRequires: libX11-devel BuildRequires: libXt-devel BuildRequires: libtheora-devel BuildRequires: libogg-devel BuildRequires: proj-devel BuildRequires: gdal-devel BuildRequires: netcdf-devel BuildRequires: netcdf-cxx-devel BuildRequires: sqlite-devel BuildRequires: libharu-devel BuildRequires: cli11-devel BuildRequires: viskores-devel BuildRequires: gl2ps-devel BuildRequires: gdcm-devel BuildRequires: qt6-qtbase-devel BuildRequires: qt6-qtdeclarative-devel # Vendored components compiled into the binaries Provides: bundled(geometry-central) = 0^git%{gc_short} Provides: bundled(happly) = 0^git%{happly_short} Provides: bundled(libigl) = %{libigl_version} Provides: bundled(trimesh2) Provides: bundled(tinyxml) Provides: bundled(robin_hood) # Pin the exact VTK ABI this package was built against. VTK freezes its soname at # libvtk*.so.1 across all of 9.x, so rpm cannot tell 9.5 from 9.6 from 9.7: a consumer # built against one installs cleanly against another and fails at LOAD with undefined # symbols. A >= floor does not help -- 9.7.0 satisfies >= 9.6.2 -- so this is an exact # match. %%vtk_abi comes from vtk-devel's macros.vtk, so it re-pins on every rebuild. # # The missing-macro check lives in %%build, deliberately, NOT at parse time. A # parse-time %%error cannot distinguish "COPR is generating the SRPM" from "this is a # real buildroot": rpkg srpm parses the spec with no buildroot and therefore no # vtk-devel, so a parse-time guard killed every SCM build of every guarded package # before any chroot existed (python-simpleitk 10980296 failed in 0 minutes with # "can't parse specfile"). Expanding to UNSET keeps the SRPM parse alive, and the # %%build test still fails loudly in a buildroot lacking vtk-devel, so no RPM carrying # the placeholder can ever be produced. Requires: vtk(abi) = %{?vtk_abi}%{!?vtk_abi:UNSET} %description ShapeWorks (SCI Institute, University of Utah) is a statistical shape modeling toolkit. It builds compact statistical models of anatomical shape populations using a particle-based optimization that places dense, corresponding landmarks across a cohort, then performs population-level PCA. It is widely used for subcortical shape analysis (complementing FSL FIRST), morphometrics, and shape-based hypothesis testing. This package provides the command-line tools and the Python API. The Qt Studio GUI is not built (it depends on an out-of-tree patched Qt5 plotting library). %package -n python3-shapeworks Summary: Python API for ShapeWorks %{?python_provide:%python_provide python3-shapeworks} Requires: %{name} = %{version}-%{release} %description -n python3-shapeworks The shapeworks_py Python module exposing the ShapeWorks Image, Mesh, Optimize, Groom and Analyze APIs for scripting shape-modeling pipelines. %package devel Summary: Development headers and CMake config for ShapeWorks Requires: %{name} = %{version}-%{release} %description devel C++ headers and CMake package configuration for building against ShapeWorks. %prep %setup -q -n ShapeWorks-%{version} # ITK_LEGACY_REMOVE: implicit Vector -> Point conversion is gone. # # Libs/Common/Region.h:93: # error: could not convert 'max.itk::Point::operator-(min)' from # 'itk::Vector' to 'shapeworks::Point' {aka 'itk::Point'} # Point size() const { return max - min; } # # itkPoint.h:104-125 is the mechanism: the TEMPLATED pass-through constructor # `Point(const TPointValueType & v)` is implicit under legacy and `explicit` # without it. A Vector matched that template, so the return statement # converted silently. Point(max - min) invokes the same constructor # explicitly, so the behaviour is identical. # # Worth knowing before "fixing" this a different way: shapeworks DOES define # its own `P operator-(const P&, const P&)` returning P for Point # (Shapeworks.h:152), but it never wins here. ITK's member # Point::operator-(const Point&) is a non-template exact match and beats the # free function template, so this expression has always produced a Vector and # has always relied on the implicit conversion. Forcing shapeworks' operator # instead would change which code runs; this does not. # # Scope limitation, stated rather than papered over: unlike the TTK blocks, # this check CANNOT be made class-level. "An implicit Vector-to-Point # conversion" is a property of overload resolution, not a token a grep can # find, so only a compile discovers new sites. The two other candidates in # this tree were checked by hand and are fine -- Image.h:231 returns # GetOrigin() which is already a Point, and Image.h:234's operands resolve to # shapeworks' own operators, which return Point. grep -qE 'return[[:space:]]+max[[:space:]]*-[[:space:]]*min;' Libs/Common/Region.h \ || { echo "ERROR: the implicit Vector->Point return is gone -- drop this block" >&2; exit 1; } sed -i 's|return max - min;|return Point(max - min);|' Libs/Common/Region.h grep -qE 'return[[:space:]]+Point\(max[[:space:]]*-[[:space:]]*min\);' Libs/Common/Region.h \ || { echo "ERROR: the explicit Point(max - min) rewrite did not apply" >&2; exit 1; } # The fix must not disturb shapeworks' own operator overloads. # CLASS-level, over the alias set DERIVED FROM THE SOURCE rather than guessed. # Three releases were lost to guessing a shorter list: -5 covered only the # return site above; -6 added Point3 declarations, but its survey pattern # matched `Point` followed by whitespace so `Point3` had been invisible to it; # -6 then failed on PointType, a third alias, at sites whose operands are # METHOD CALLS rather than bare identifiers -- which that pattern could not # have matched either. # # Every alias of itk::Point in this tree, enumerated from its own typedefs: # Point, Point3, IPoint3, FPoint3, PointType. The RHS pattern is now "contains # a '-' operator" rather than "identifier - identifier". # # A broad rewrite is safe HERE specifically, which is what makes it # acceptable: direct-initialisation works whether the RHS is a Vector # (explicit templated ctor, itkPoint.h:110) or already a Point (copy ctor). # Converting a site that did not strictly need it therefore costs nothing. # Image.cpp:420 is such a site -- it compiled fine -- and is converted rather # than special-cased, because an exception list is one more thing to get wrong. for f in $(grep -rlE '(^|[^A-Za-z0-9_])(Point|Point3|IPoint3|FPoint3|PointType)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=[^;=]*[^-]-[^->][^;]*;' \ --include=*.h --include=*.cpp --include=*.cxx Libs/ Applications/); do sed -i -E 's#^([[:space:]]*)(const[[:space:]]+)?(Point|Point3|IPoint3|FPoint3|PointType)([[:space:]]+[A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*([^;]*-[^;]*);#\1\2\3\4(\5);#' "$f" done _sw_decl=$(grep -rnE '(^|[^A-Za-z0-9_])(Point|Point3|IPoint3|FPoint3|PointType)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=[^;=]*[^-]-[^->][^;]*;' \ --include=*.h --include=*.cpp --include=*.cxx Libs/ Applications/ | wc -l) test "$_sw_decl" -eq 0 \ || { echo "ERROR: Point-alias still copy-initialised from an expression containing '-':" >&2 grep -rnE '(^|[^A-Za-z0-9_])(Point|Point3|IPoint3|FPoint3|PointType)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=[^;=]*[^-]-[^->][^;]*;' \ --include=*.h --include=*.cpp --include=*.cxx Libs/ Applications/ >&2; exit 1; } _sw_ops=$(grep -cE '^P operator[-+*/]\(' Libs/Common/Shapeworks.h) test "$_sw_ops" -eq 6 \ || { echo "ERROR: expected 6 shapeworks operator overloads, found $_sw_ops" >&2; exit 1; } %patch -P0 -p1 # --- Vendored source trees (kept out of the ShapeWorks source dir proper) --- mkdir -p _vendor # geometry-central (pinned commit) + its happly submodule tar -xzf %{SOURCE1} -C _vendor tar -xzf %{SOURCE2} -C _vendor # place happly into geometry-central's deps/happly (submodule, absent from archive) cp -a _vendor/happly-%{happly_commit}/. _vendor/geometry-central-%{gc_commit}/deps/happly/ # libigl 2.3.0 full source tree, laid out as FindLIBIGL expects: /libigl/... mkdir -p _vendor/libigl-root/libigl tar -xzf %{SOURCE4} -C _vendor/libigl-root/libigl --strip-components=1 # --- Eigen 5: its config version file reads "3.3" as 3.3.x only, so ask for a # range. ITK (>= 5.4.7-4) compiles against this same system Eigen. --- sed -i 's/^find_package (Eigen3 3\.3 REQUIRED NO_MODULE)$/find_package (Eigen3 3.3...5 REQUIRED NO_MODULE)/' CMakeLists.txt grep -qF 'find_package (Eigen3 3.3...5 REQUIRED NO_MODULE)' CMakeLists.txt # --- GCC15 fix: ShapeworksUtils.cpp uses unqualified cout/endl --- sed -i '108s/cout/std::cout/; 108s/endl/std::endl/' Libs/Common/ShapeworksUtils.cpp # --- Qt5 linkage: with Studio off, the Qt5 link dependency does not propagate to # the CLI exe or the Python module. Link Qt5 by plain library name (-lQt5*), # NOT the imported targets, to avoid a QT_MAJOR_VERSION clash with VTK's Qt6. --- sed -i 's/^ pybind11::embed Project Image Groom Analyze Application/ pybind11::embed Project Image Groom Analyze Application\n Qt5Widgets Qt5Gui Qt5Core/' \ Applications/shapeworks/CMakeLists.txt sed -i 's/^ ${DTAG_EXTRA_FLAGS}/ Qt5Widgets Qt5Gui Qt5Core\n ${DTAG_EXTRA_FLAGS}/' \ Libs/Python/CMakeLists.txt # --- Bundled libigl vs newer Eigen (3.4.90 and 5.x removed DynamicSparseMatrix/ # MappedSparseMatrix). libigl 2.3.0 targets stock Eigen <=3.4.0. --- pushd _vendor/libigl-root/libigl/include/igl >/dev/null # 1) the removed deprecated class -> modern SparseMatrix (reserve/coeffRef/convert) for f in cat.cpp diag.cpp slice_into.cpp; do sed -i 's/DynamicSparseMatrix/SparseMatrix/g' "$f" done # 2) the umbrella drags in those removed classes; # none of these files use any SparseExtra-only feature, so use for f in cat.cpp cotmatrix.cpp diag.cpp min_quad_with_fixed.cpp slice_into.cpp \ vector_area_matrix.cpp min_quad_with_fixed.h; do sed -i 's|#include *|#include |' "$f" done popd >/dev/null %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; } vendor="$(pwd)/_vendor" # (1) Build geometry-central as a -fPIC static lib against the system Eigen # (the Python module is a shared object, so its static deps must be PIC). # Its FindEigen3.cmake reads the version from Eigen/src/Core/util/Macros.h, # which Eigen 5 moved to Eigen/Version, and then falls back to a network # download. Define Eigen3::Eigen up front instead; geometry-central uses an # existing target as-is. echo 'find_package(Eigen3 REQUIRED CONFIG)' > ${vendor}/gc-eigen.cmake export CXXFLAGS="%{optflags} -std=c++17 -include cstdint -fPIC" %__cmake -S _vendor/geometry-central-%{gc_commit} -B _vendor/gc-build -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ -DCMAKE_PROJECT_INCLUDE=${vendor}/gc-eigen.cmake %__cmake --build _vendor/gc-build --target geometry-central %{?_smp_mflags} # stage geometry-central (no install rules at this commit) mkdir -p _vendor/gc-stage/lib _vendor/gc-stage/include cp _vendor/gc-build/src/libgeometry-central.a _vendor/gc-stage/lib/ cp -a _vendor/geometry-central-%{gc_commit}/include/geometrycentral _vendor/gc-stage/include/ cp _vendor/geometry-central-%{gc_commit}/deps/happly/happly.h _vendor/gc-stage/include/ cp -a _vendor/geometry-central-%{gc_commit}/deps/nanoflann/include/nanoflann _vendor/gc-stage/include/ cp -a _vendor/geometry-central-%{gc_commit}/deps/nanort/include/nanort _vendor/gc-stage/include/ 2>/dev/null || : # (2) Configure and build ShapeWorks (CLI + Python), all system libraries. export GEOMETRYCENTRAL_DIR=${vendor}/gc-stage export LIBIGL_DIR=${vendor}/libigl-root export CXXFLAGS="%{optflags} -std=c++17 -include cstdint -include cassert -fPIC" %cmake -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DBUILD_STUDIO=OFF \ -DBUILD_TESTS=OFF \ -DBUILD_DOCUMENTATION=OFF \ -DUSE_OPENMP=ON \ -DITK_DIR=%{_prefix}/lib/cmake/ITK-5.4 \ -DVTK_DIR=%{_libdir}/cmake/vtk \ -DOpenVDB_DIR=%{_libdir}/cmake/OpenVDB \ -DEigen3_DIR=%{_datadir}/cmake/eigen3 %cmake_build %install %cmake_install # Relocate the Python module out of %{_bindir} into the Python sitearch mkdir -p %{buildroot}%{python3_sitearch} mv %{buildroot}%{_bindir}/shapeworks_py*.so %{buildroot}%{python3_sitearch}/ # Strip any leftover RPATHs for Fedora compliance chrpath --delete %{buildroot}%{_bindir}/shapeworks 2>/dev/null || : chrpath --delete %{buildroot}%{python3_sitearch}/shapeworks_py*.so 2>/dev/null || : %files %license LICENSE.txt %doc README.md %{_bindir}/shapeworks %files -n python3-shapeworks %license LICENSE.txt %{python3_sitearch}/shapeworks_py*.so %files devel %{_includedir}/* # ShapeWorks hardcodes CMAKE_INSTALL_LIBDIR=lib, so its libs/config land in /usr/lib %{_prefix}/lib/*.a %{_prefix}/lib/cmake/ShapeWorks/ %changelog * Mon Oct 05 2026 Morgan Hough - 6.7.0-7 - Two more sites, PointType this time (ContourDomain.h:52, MeshDomain.cpp:54), one with METHOD CALLS as operands rather than bare identifiers. Both were invisible to -6's pattern, for two independent reasons. - Three releases lost to guessing which aliases exist, so stop guessing: the set is enumerated from the tree's own typedefs (Point, Point3, IPoint3, FPoint3, PointType) and the RHS pattern is "contains a '-' operator". Six sites, all converted to direct-initialisation. - The broad rewrite is safe here specifically: direct-init works whether the RHS is a Vector (explicit templated ctor) or already a Point (copy ctor), so converting a site that did not need it costs nothing. Image.cpp:420 is converted rather than special-cased -- an exception list is one more thing to get wrong. - Unchanged honest boundary: RETURN statements and arbitrary expressions cannot be covered this way; Region.h's return is handled by name above, and any other would need a compile to find. * Mon Oct 05 2026 Morgan Hough - 6.7.0-6 - Three more sites of the same construct, in Libs/Particles/ReconstructSurface.cpp (1162, 1163, 1169): Point3 variables copy-initialised from a Point subtraction, which yields a Vector. Direct-initialisation instead. - -5 fixed only the Region.h RETURN site and shipped. The reason the others were missed is worth recording: my inventory grep matched `Point` followed by whitespace, and these declare `Point3`. The typedef alias defeated the pattern, exactly as an unescaped `()` defeated the ComputeBoundingBox scan in SPHARM-PDM earlier today. Both times the pattern was narrower than the thing it was looking for, and both times it reported clean. - The residual check is now class-level over every Point alias and pinned at zero, so a new declaration site fails in %%prep rather than mid-build. The limitation noted in -5 still stands for RETURN statements and arbitrary expressions: an implicit conversion is a property of overload resolution, not a token, and only a compile finds those. Image.cpp:420 and Image.h:234 use shapeworks' own operator+ and are fine -- confirmed by their compiling. * Mon Oct 05 2026 Morgan Hough - 6.7.0-5 - ITK_LEGACY_REMOVE removed the implicit Vector -> Point conversion, so Region.h's `Point size() const { return max - min; }` no longer compiles. itkPoint.h:104-125 makes the templated pass-through constructor explicit; Point(max - min) invokes the same constructor explicitly, so behaviour is unchanged. - shapeworks defines its own P operator-(const P&, const P&) returning P (Shapeworks.h:152), but it never won here: ITK's member Point::operator-(const Point&) is a non-template exact match and beats the free template. This expression has always produced a Vector and always relied on the implicit conversion. Forcing shapeworks' operator instead would change which code runs; this does not. %%prep pins the overload count so the fix cannot drift into touching them. - Scope limitation, stated rather than hidden: unlike the TTK blocks this check cannot be class-level. An implicit Vector-to-Point conversion is a property of overload resolution, not a token a grep can find, so only a compile discovers new sites. The tree's two other candidates were checked by hand and are fine. - Surfaced on fedora-rawhide-x86_64, where shapeworks was one of three genuinely stale .fc45 builds still linking libitkvnl-5.4.so.1, which InsightToolkit5 5.4.7-21 no longer ships. - RED/GREEN via ci/prep-tdd.sh before building. * Wed Sep 16 2026 Morgan Hough - 6.7.0-4 - Build against the system Eigen (eigen3-static, 5.0.1) instead of ITK's bundled itkeigen, which InsightToolkit5 no longer installs since 5.4.7-4. ShapeWorks must match ITK's Eigen in every ITK-including translation unit, so this follows ITK rather than choosing independently. -3 failed: "Eigen3::Eigen includes non-existent path /usr/include/ITK-5.4/itkeigen". - Drop the bundled Eigen 3.4.0 (Source3) and its bundled(eigen3) Provides; the system eigen3-devel ships unsupported/ too. geometry-central now builds against the system Eigen as well, so the static lib and ShapeWorks agree. - Record -3 (6eff8dd), which had no changelog entry: ITK_DIR now points at %%{_prefix}/lib/cmake/ITK-5.4, where ITK's config actually lives. * Sun Aug 23 2026 Morgan Hough - 6.7.0-2 - 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. * Thu Jun 18 2026 Morgan Hough - 6.7.0-1 - Initial package: ShapeWorks 6.7.0 CLI + Python API, no superbuild, all system libraries. Studio GUI disabled (out-of-tree patched JKQTPlotter Qt5 fork). - Bundles geometry-central (pinned commit), happly, libigl 2.3.0 and Eigen 3.4.0; patches bundled libigl for Eigen >=3.4.90 (ITK's itkeigen removed DynamicSparseMatrix/MappedSparseMatrix).