%global commit 16ea1366df8dac6cafe744b134dffc1635e5146d %global shortcommit %(c=%{commit}; echo ${c:0:7}) %define debug_package %{nil} Name: c3d Version: 1.4.6 Release: 7%{?dist} Summary: Medical image format conversion and processing tool License: GPL-3.0-or-later URL: https://github.com/pyushkevich/c3d Source0: https://github.com/pyushkevich/c3d/archive/%{commit}/%{name}-%{commit}.tar.gz BuildRequires: cmake BuildRequires: gcc-c++ BuildRequires: InsightToolkit5-devel >= 5.4 # Pin the ITK this was BUILT against. ITK freezes its sonames across releases # while ADDING symbols: libITKFFT-5.4.so.1 is identical in name in 5.4.7-2 and # 5.4.7-8, but -8 enabled FFTW and exports # _ZN3itk46FFTWFFTImageFilterInitFactoryRegister__PrivateEv. rpm derives # dependencies from sonames, so it cannot tell them apart -- c3d installs cleanly # over the older ITK and then exits 127 on EVERY invocation. Measured on a real # host by the framework session, then confirmed per object here: 3 of c3d's 4 # shipped ELF objects need that symbol (c3d, c2d, c4d). # # A floor, not an equality: the breakage is additive, so a newer ITK is fine. # # The two-part %%{?itk_ver}%%{!?itk_ver:UNSET} expansion is required, not # stylistic. ">= UNSET" is SATISFIED -- rpm ranks a numeric segment above an # alphabetic one -- so an undefined macro makes this floor vacuous, which is what # the %%build guard catches. It must also be this shape rather than a parse-time # %%global check: a parse-time guard once killed every SCM build before a chroot # existed (python-simpleitk 10980296, "can't parse specfile", 0 minutes). Requires: InsightToolkit5%{?_isa} >= %{?itk_ver}%{!?itk_ver:UNSET} BuildRequires: hdf5-devel BuildRequires: eigen3-devel # VTK transitive deps (ITK5 cmake config loads ITKVtkGlue → find_package(VTK)) # vtk-devel should Require these but does not yet BuildRequires: python3-devel BuildRequires: qt6-qtdeclarative-devel %if 0%{?fedora} BuildRequires: libminc-devel %endif %description Convert3D (c3d) is a command-line tool for converting between medical image formats and performing various image processing operations. It supports NIfTI, DICOM, MetaImage, Analyze, and other common formats. The suite includes c3d (3D), c2d (2D), c4d (4D), and c3d_affine_tool for working with affine transformations. %prep %setup -q -n c3d-%{commit} # ITK 5.4.7-10 removed its legacy API (ITK's CMake forces ITK_LEGACY_REMOVE=ON # when ITK_WRAP_PYTHON is enabled), and c3d CALLS one of the removed methods: # # c3d: symbol lookup error: c3d: undefined symbol: # _ZN3itk19ProgressAccumulator45ResetFilterProgressAndKeepAccumulatedProgressEv # # c3d could not start at all -- and note this is a genuine CALL, not the vtable # reference that broke ANTs. Measured with readelf: one R_X86_64_JUMP_SLOT # relocation, i.e. a PLT call site, versus ANTs' 8 R_X86_64_64 entries in # .data.rel.ro. That distinction decides the remedy: ANTs needed only a # rebuild, because recompiling simply emits a vtable without the slots; c3d # would fail to COMPILE against the new headers, because the declaration is # gone. (It dies immediately rather than on first use because Fedora links # with -z now, so even PLT relocations are resolved eagerly.) # # Deleting the call is correct, not merely expedient. ITK's own header says of # this method: "the implementation of this method is now empty" -- the # ProgressAccumulator tracks restarted filters by itself, and the old body's # SetProgress(0) had the documented side effect of forcing filters to rerun. # So the call has done nothing for several ITK releases. grep -q 'ResetFilterProgressAndKeepAccumulatedProgress' \ itkextras/itkMultiScaleHessianBasedMeasureImageFilter.hxx \ || { echo "ERROR: the legacy ITK call is gone -- drop this %%prep block" >&2; exit 1; } sed -i '/progress->ResetFilterProgressAndKeepAccumulatedProgress();/d' \ itkextras/itkMultiScaleHessianBasedMeasureImageFilter.hxx ! grep -rq 'ResetFilterProgressAndKeepAccumulatedProgress' . \ || { echo "ERROR: a call to the removed ITK method survives:" >&2 grep -rn 'ResetFilterProgressAndKeepAccumulatedProgress' . >&2; exit 1; } # SECOND legacy ITK dependency, surfaced by -5 once the first was out of the # way: itk::SpatialOrientation is a compatibility namespace of `using` aliases # living entirely inside #ifndef ITK_LEGACY_REMOVE (itkSpatialOrientation.h), # so with legacy removed the name does not exist and c3d does not compile: # adapters/PrintImageInfo.cxx:52: error: 'itk::SpatialOrientation' has not # been declared # The modern spelling is what those aliases point at. Exactly two forms are # used, 100 occurrences across 5 files, and the rewrite is mechanical: # SpatialOrientation::ValidCoordinateOrientationFlags # -> SpatialOrientationEnums::ValidCoordinateOrientations # SpatialOrientation::ITK_COORDINATE_ORIENTATION_x # -> SpatialOrientationEnums::ValidCoordinateOrientations::ITK_COORDINATE_ORIENTATION_x # Order matters: the ORIENTATION_ rewrite must run first or the general form # would swallow it. Checked that nothing converts these to int -- the new form # is an `enum class`, so an implicit conversion that used to compile would not. grep -rq 'SpatialOrientation::ITK_COORDINATE_ORIENTATION_' . \ || { echo "ERROR: the legacy SpatialOrientation aliases are gone -- drop this block" >&2; exit 1; } grep -rl 'SpatialOrientation' --include=*.cxx --include=*.h --include=*.hxx . \ | xargs sed -i \ -e 's/SpatialOrientation::ITK_COORDINATE_ORIENTATION_/SpatialOrientationEnums::ValidCoordinateOrientations::ITK_COORDINATE_ORIENTATION_/g' \ -e 's/SpatialOrientation::ValidCoordinateOrientationFlags/SpatialOrientationEnums::ValidCoordinateOrientations/g' if grep -ro 'SpatialOrientation::[A-Za-z_]*' . | grep -v SpatialOrientationEnums | grep -q .; then echo "ERROR: legacy SpatialOrientation references survive:" >&2 grep -rno 'SpatialOrientation::[A-Za-z_]*' . | grep -v SpatialOrientationEnums >&2 exit 1 fi # One file resisted the qualified-name rewrite above, and local mock is what # found it -- SwapDimensions.cxx pulls the whole legacy namespace in with a # using-directive and then names the enumerators bare: # using namespace itk::SpatialOrientation; # emap["RIP"] = ITK_COORDINATE_ORIENTATION_RIP; x48 # `using namespace` cannot work against the replacement, because # SpatialOrientationEnums is a CLASS, not a namespace. And the enumerators now # live in an `enum class`, so they have to be qualified individually; C++20's # `using enum` would collapse this to one line but c3d is pinned to C++17 # (CMAKE_CXX_STANDARD 17). # So: turn the directive into a type alias of the same name -- which keeps the # file's own signatures and std::map declarations compiling unchanged -- and # qualify the 48 bare enumerators through it. grep -q '^using namespace itk::SpatialOrientation;$' adapters/SwapDimensions.cxx \ || { echo "ERROR: the using-directive is gone -- drop this block" >&2; exit 1; } sed -i \ -e 's|^using namespace itk::SpatialOrientation;$|using ValidCoordinateOrientationFlags = itk::SpatialOrientationEnums::ValidCoordinateOrientations;|' \ -e 's/\([^:A-Za-z0-9_]\)ITK_COORDINATE_ORIENTATION_/\1ValidCoordinateOrientationFlags::ITK_COORDINATE_ORIENTATION_/g' \ -e 's/^ITK_COORDINATE_ORIENTATION_/ValidCoordinateOrientationFlags::ITK_COORDINATE_ORIENTATION_/' \ adapters/SwapDimensions.cxx if grep -qE '(^|[^:A-Za-z0-9_])ITK_COORDINATE_ORIENTATION_' adapters/SwapDimensions.cxx; then echo "ERROR: unqualified ITK_COORDINATE_ORIENTATION_ enumerators survive:" >&2 grep -nE '(^|[^:A-Za-z0-9_])ITK_COORDINATE_ORIENTATION_' adapters/SwapDimensions.cxx >&2 exit 1 fi %build test -n "%{?itk_ver}" || { echo "ERROR: itk_ver undefined - add 'BuildRequires: InsightToolkit5-devel'" >&2; exit 1; } export CXXFLAGS="%{optflags} -std=c++17 -include cstdint -fpermissive" %cmake \ -DCMAKE_CXX_STANDARD:STRING=17 \ -DBUILD_SHARED_LIBS:BOOL=OFF \ -DBUILD_GUI:BOOL=OFF \ -DBUILD_TESTING:BOOL=OFF \ -DCONVERT3D_USE_ITK_REMOTE_MODULES:BOOL=OFF \ -DITK_DIR:PATH=%{_prefix}/lib/cmake/ITK-5.4 \ -DCMAKE_SKIP_INSTALL_RPATH:BOOL=ON %cmake_build %install %cmake_install # Remove cmake export files generated in build dir (triggers check-files error) find %{_builddir} -name "Convert3DTargets*.cmake" -delete # Remove development files — no -devel subpackage rm -rf %{buildroot}%{_includedir} rm -rf %{buildroot}%{_libdir}/cmake rm -rf %{buildroot}/usr/lib/cmake rm -f %{buildroot}%{_libdir}/*.a rm -f %{buildroot}/usr/lib/*.a rm -f %{buildroot}%{_libdir}/*.so rm -f %{buildroot}/usr/lib/*.so %files %license COPYING.txt %doc README.md %{_bindir}/c2d %{_bindir}/c3d %{_bindir}/c4d %{_bindir}/c3d_affine_tool %changelog * Mon Sep 28 2026 Morgan Hough - 1.4.6-7 - Finish the SpatialOrientation port: SwapDimensions.cxx uses a `using namespace itk::SpatialOrientation;` directive plus 48 bare enumerators, which the qualified-name rewrite in -6 could not reach. The directive cannot be carried over -- SpatialOrientationEnums is a class, not a namespace -- and the enumerators are now in an `enum class`, so each needs qualifying (C++20's `using enum` would do it in one line, but c3d pins CMAKE_CXX_STANDARD 17). Replaced with a same-named type alias so the file's own signatures and std::map declarations compile unchanged. - Found in LOCAL MOCK, which is where this should have been all along: -5 and -6 were each sent to COPR and each failed on all 7 chroots for a reason one local build would have shown in five minutes. Note plain `mock -r fedora-44-x86_64` is not enough for this package -- it dies in 20 seconds on "No match for argument: InsightToolkit5-devel >= 5.4" because the COPR is not in the chroot. Needs --addrepo=https://download.copr.fedorainfracloud.org/results/mhough/neurofedora/fedora-44-x86_64/ * Mon Sep 28 2026 Morgan Hough - 1.4.6-6 - Port off itk::SpatialOrientation, the second legacy ITK dependency, which -5 surfaced once the first was fixed: the whole namespace is `using` aliases inside #ifndef ITK_LEGACY_REMOVE, so with legacy gone c3d does not compile ("'itk::SpatialOrientation' has not been declared", PrintImageInfo.cxx:52). Rewrite to the modern SpatialOrientationEnums::ValidCoordinateOrientations that those aliases point at -- two forms, 100 occurrences, 5 files, checked that nothing relies on an implicit conversion to int (the new form is an enum class, so that would no longer compile). Guarded in both directions and dry-run against the real tarball before building. - Worth recording the shape of this: -4 could not start, -5 could not compile, and that is forward progress. Each fix exposes the next legacy API rather than the package having several independent bugs. Fixing them one COPR build at a time is the wrong loop; this one was iterated in local mock. * Mon Sep 28 2026 Morgan Hough - 1.4.6-5 - c3d could not start at all: ITK 5.4.7-10 removed itk::ProgressAccumulator::ResetFilterProgressAndKeepAccumulatedProgress() along with the rest of its legacy API, and c3d calls it. Every invocation died in the loader, `c3d -version` included. - Unlike ANTs, a rebuild is NOT enough here. readelf puts the difference precisely: c3d has one R_X86_64_JUMP_SLOT for the symbol -- a PLT call site, so the source genuinely calls it and would now fail to compile -- where ANTs had 8 R_X86_64_64 entries in .data.rel.ro, vtable slots that simply vanish on recompilation. Same ITK change, two different remedies, and the relocation type is what tells them apart. - Delete the call in %%prep. ITK's own header documents the method as having an empty implementation now: the ProgressAccumulator detects restarted filters itself, and the old body's SetProgress(0) forced needless filter reruns. The call has been a no-op for several releases. Guarded both ways -- the %%prep block fails if the call is already gone, and fails if any call survives. * Fri Sep 25 2026 Morgan Hough - 1.4.2-4 - Pin the ITK built against, with a %%build guard. c3d had NO runtime ITK dependency beyond sonames and a vacuous "InsightToolkit5-devel >= 5.4" -- a devel floor on a runtime package, pinned six releases stale. - Without this, c3d installs over ITK 5.4.7-2 and exits 127 on every invocation: 3 of its 4 shipped ELF objects (c3d, c2d, c4d) need the FFTW registration symbol that only 5.4.7-8 and later export, and the SONAME does not change, so rpm cannot see it. Found by the framework session on a real host. - Ownership: legion, assigned by Morgan 2026-09-25. Previously unowned. * Sat Sep 19 2026 Morgan Hough - 1.4.6-3 - Rebuild against InsightToolkit5 5.4.7-8, which unbundles Eigen3, VXL and double-conversion. No source or spec change; the Release bump is what makes the rebuild reach anyone. COPR adds a sibling at an already-published NVR rather than replacing it, and dnf then serves the older copy -- so a rebuild at the same NVR would leave this package linked against the bundled ITK while appearing to succeed. - Validated against -8 in mhough/neurofedora-itk5-test before this bump. * Thu Apr 23 2026 Morgan Hough - 1.4.6-2 - Update to 1.4.6 - Add eigen3-devel BuildRequires (ITK5 cmake config requires Eigen3) * Wed Mar 18 2026 Morgan Hough - 1.4.2-4 - Remove cmake export target files from build dir (unpackaged files error) - Add VTK transitive BuildRequires (python3-devel, qt6-qtdeclarative-devel) * Wed Mar 18 2026 Morgan Hough - 1.4.2-1 - Initial package of Convert3D 1.4.2 (git snapshot %{shortcommit}) - Built against system InsightToolkit5 with static internal libraries