NIfTI: track the maintained nifti_clib fork, modernise the CMake plumbing - #230
NIfTI: track the maintained nifti_clib fork, modernise the CMake plumbing#230gdevenyi wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the superbuild to always build NIfTI from a pinned, maintained nifti_clib fork (ITK/InsightSoftwareConsortium) and removes the dead “system NIFTI” path and legacy FindNIFTI.cmake, ensuring symbol-mangling avoids collisions with ITK’s bundled niftiio.
Changes:
- Remove the unreachable
USE_SYSTEM_NIFTIbranch and always build NIfTI fromBuildNIFTI.cmake. - Pin
InsightSoftwareConsortium/nifti_clibto a specific commit SHA and addNIFTI::niftiio/NIFTI::znzimported targets mirroringNIFTIConfig.cmake. - Delete the legacy
cmake-modules/FindNIFTI.cmake.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| CMakeLists.txt | Removes the dead system-NIFTI branch and makes NIFTI a consistent superbuild dependency. |
| cmake-modules/BuildNIFTI.cmake | Pins the maintained fork and adds imported targets for consistent consumer linking. |
| cmake-modules/FindNIFTI.cmake | Removes obsolete find-module for the dropped system-NIFTI path. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # the ADD_SUBDIRECTORY()'d consumers are configured. | ||
| # Build ordering is not carried by these targets -- keep the explicit | ||
| # add_dependencies(<target> NIFTI) calls at the call sites. | ||
| file(MAKE_DIRECTORY ${NIFTI_INCLUDE_DIR}) # imported INTERFACE include dirs must exist at configure time |
There was a problem hiding this comment.
Fixed in the review commit.
The quoting is a real issue — unquoted, a prefix containing spaces splits into several arguments and creates the wrong directories. Now:
FILE(MAKE_DIRECTORY "${NIFTI_INCLUDE_DIR}" "${ZNZ_INCLUDE_DIR}")On the second half: ZNZ_INCLUDE_DIR is set to the same string as NIFTI_INCLUDE_DIR a few lines up (${staging_prefix}/${install_prefix}/include/nifti), so it was already being created — but relying on that is fragile, so both are now named explicitly.
| set_target_properties(NIFTI::znz PROPERTIES | ||
| IMPORTED_LOCATION "${ZNZ_LIBRARY}" | ||
| INTERFACE_INCLUDE_DIRECTORIES "${ZNZ_INCLUDE_DIR}" | ||
| INTERFACE_LINK_LIBRARIES "${ZLIB_LIBRARY}") |
There was a problem hiding this comment.
Agreed, fixed in the review commit — and the pointer to BuildZLIB.cmake was the useful part.
NIFTI::znz now links ZLIB::ZLIB. Checked it exists on both paths before build_nifti() runs: USE_SYSTEM_ZLIB=ON goes through FIND_PACKAGE(ZLIB REQUIRED), and OFF goes through build_zlib(), which creates it as a GLOBAL imported target at BuildZLIB.cmake:113. build_zlib() is called well before build_nifti(), so ordering is fine.
Following that file the rest of the way, I also added ADD_DEPENDENCIES(NIFTI::niftiio NIFTI) / ADD_DEPENDENCIES(NIFTI::znz NIFTI), mirroring its ADD_DEPENDENCIES(ZLIB::ZLIB ZLIB). Build ordering now rides on the imported target itself rather than only on the add_dependencies calls at each call site.
The note said USE_SYSTEM_NIFTI has no option() entry and must be passed on the command line. BIC-MNI#230 makes it a real option(), so that stops being true, and the table row above it stops needing the explanation. What is worth documenting is the trade-off. The bundled NIfTI has every exported symbol renamed to minc_* so it cannot collide with the unmangled niftiio ITK bundles; a system NIfTI is whatever the distribution shipped. That is fine against the shared libniftiio.so distributions package, which is what the release .deb and .rpm link, but pairing USE_SYSTEM_NIFTI with a static system NIfTI and MT_BUILD_ITK_TOOLS collides at link time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
2510a2f to
415a471
Compare
The note said USE_SYSTEM_NIFTI has no option() entry and must be passed on the command line. BIC-MNI#230 makes it a real option(), so that stops being true, and the table row above it stops needing the explanation. What is worth documenting is the trade-off. The bundled NIfTI has every exported symbol renamed to minc_* so it cannot collide with the unmangled niftiio ITK bundles; a system NIfTI is whatever the distribution shipped. That is fine against the shared libniftiio.so distributions package, which is what the release .deb and .rpm link, but pairing USE_SYSTEM_NIFTI with a static system NIfTI and MT_BUILD_ITK_TOOLS collides at link time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
|
Rebased onto
Re-verified on the new base, both modes, against an installed system
One thing worth knowing rather than fixing: |
The note I added claimed a system NIfTI was fine against the shared libniftiio.so distributions ship, and that the hazard was a link-time clash on nifti_image_read with a static one. Both halves were wrong. ITK renames its bundled niftiio but not its znzlib, so the clash is on znzopen and seven siblings, not nifti_image_read. And shared is precisely the case that breaks: ITK's copies interpose over libznz.so at runtime, so nothing fails at link time and reads simply start returning nothing. BIC-MNI#230 now refuses the combination outright, so describe that rather than a caveat the reader has to police. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
|
Added a fourth commit (e7cbe31) in response to @vfonov on BIC-MNI/libminc#152, who pointed out that a system NIfTI collides with ITK's. It does, and I had the mechanism wrong in this PR's earlier description. What I got wrong. I claimed the clash was on
Nothing fails at link time. Reproduced against the installed system The change.
This keeps the option where a distribution packager would actually want it while making the broken combination unreachable. CI and the release workflow both build #227 carries the matching README correction — the caveat I wrote there described the wrong symbols and the wrong linkage mode. |
The note I added claimed a system NIfTI was fine against the shared libniftiio.so distributions ship, and that the hazard was a link-time clash on nifti_image_read with a static one. Both halves were wrong. ITK renames its bundled niftiio but not its znzlib, so the clash is on znzopen and seven siblings, not nifti_image_read. And shared is precisely the case that breaks: ITK's copies interpose over libznz.so at runtime, so nothing fails at link time and reads simply start returning nothing. BIC-MNI#230 now refuses the combination outright, so describe that rather than a caveat the reader has to police. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
|
Correcting my own evidence above (commit 2572521). The claim that the system
The interposition is real and measurable; the breakage is not, on LP64 with both sides built
The guard and the behaviour are unchanged — only the comment, the error message and the README note in #227, which now say this instead of the overstated version. |
|
The upstream fix landed, so the restriction is gone. Added in 20dee19. InsightSoftwareConsortium/ITK#6756 merged to Pin moves The Verified against the newly pinned tarball:
and all four permutations configure clean, including the one that was rejected a commit ago:
Our own NIfTI stays renamed to This closes out the packaging goal that started the thread: a release build can now use a distribution NIfTI and ship the ITK tools. #231, the local |
|
why do we still need to mangle , if ITK accepts system NIFTI? |
For the same reason ITK mangles, we're vendoring our own copy of NIFTI as part of the superbuild, so we should mangle symbols so we don't mess with the system copies if they exist. |
…ti path nifti_clib upstream (NIFTI-Imaging) has not moved since v3.0.0 in 2020. The ITK fork is the maintained line, so pin that instead; it carries no tags, so the pin is a commit SHA. Keep it in step with the identical pin in libminc/cmake-modules/BuildNIFTI.cmake, and regenerate nifti_mangle.h whenever either moves -- the fork exports symbols v3.0.0 did not, and an unmangled symbol is one that can collide with ITK's bundled niftiio. build_nifti() now also defines the NIFTI::niftiio / NIFTI::znz imported targets that nifti_clib's own NIFTIConfig.cmake provides, so the ADD_SUBDIRECTORY()'d consumers link the same way whether NIFTI came from here or from find_package(NIFTI CONFIG). The config package itself cannot be used here: ExternalProject builds at build time, long after those consumers are configured. Build ordering still rides on the existing add_dependencies(... NIFTI) calls. The USE_SYSTEM_NIFTI branch is removed rather than modernised. Its option() has been commented out for years, so FIND_PACKAGE(NIFTI REQUIRED) was unreachable, and it cannot come back: our copy is symbol-mangled to minc_* precisely so it cannot collide with the unmangled niftiio ITK bundles, and a system nifti would reintroduce that collision. Its FindNIFTI.cmake is deleted with it -- it hardcoded /usr/local/bic and reported everything it found as "NetCDF". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
Removing it went too far. The option was unreachable because its option() line was commented out, but the capability is wanted: the release packages are built against system libraries so that the .deb and .rpm depend on distribution packages, and NIfTI should be no different. It is now a real option() rather than a command-line-only variable, so it shows up in ccmake alongside the other USE_SYSTEM_* switches, and it selects find_package(NIFTI CONFIG REQUIRED) over build_nifti(). No consumer changes were needed: both paths define NIFTI::niftiio and NIFTI::znz, which is all libminc, minctools and Display ask for. The mangling caveat is now documented at the call site rather than used as a reason to drop the option. The bundled build renames its symbols to minc_* so it cannot collide with ITK's bundled ITKniftiio; a system nifti is unmangled, which is fine against the shared libniftiio.so distributions package, but not against a static one when ITK tools are enabled. LIBMINC_USE_SYSTEM_NIFTI is forwarded so libminc can skip its mangling invariant test, and NIFTI is dropped from MINC2_deps in system mode since there is no ExternalProject to wait for. Verified against an installed system nifti_clib (shared, unmangled): USE_SYSTEM_NIFTI=ON configures and links mnc2nii/nii2mnc against /usr/lib/libniftiio.so, builds no NIFTI ExternalProject, and records the system library in the exported LIBMINCConfig. USE_SYSTEM_NIFTI=OFF is unchanged. Standalone libminc passes 38 tests bundled and 37 system, the difference being the correctly-skipped mangling test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
From review of the imported-target block:
- NIFTI::znz linked the raw ${ZLIB_LIBRARY} path. Link ZLIB::ZLIB instead,
which BuildZLIB.cmake already creates for exactly this purpose, so
consumers inherit its usage requirements rather than a bare path.
- NIFTI::niftiio hardcoded m in its interface. nifti_clib itself only links
a math library where one exists (NIFTI_SYSTEM_MATH_LIB is empty on WIN32);
match that.
- file(MAKE_DIRECTORY) was unquoted, so a prefix containing spaces would
split into several arguments. Quote it, and name ZNZ_INCLUDE_DIR
explicitly rather than relying on it being the same string.
Also ADD_DEPENDENCIES() the imported targets on the NIFTI ExternalProject,
matching what BuildZLIB.cmake does for ZLIB::ZLIB, so build ordering rides
on the target itself rather than only on the add_dependencies calls at the
call sites.
Verified both ways against an installed system nifti_clib: USE_SYSTEM_NIFTI
ON and OFF both configure and generate, and nii2mnc links the expected
libraries in each.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
vfonov flagged that a system NIfTI collides with ITK's. It does, and the failure is quieter than a link error. ITK mangles its bundled niftiio -- itk_nifti_mangle.h, included from its nifti1.h, covering 103 names -- but ships no equivalent for znzlib, so ITKznz exports Xznzclose, znzopen, znzputs, znzread, znzrewind, znzseek, znztell and znzwrite unprefixed. A distribution nifti is unmangled too, so those eight clash in every binary that links both ITK and libminc: c3d, elastix, ANTs, the EZminc tools. Nothing fails at link time. ITK's copies are pulled into the executable and land in its dynamic symbol table, where they interpose over libznz.so, so the system libniftiio.so reaches ITK's znz layer for every read. Reproduced against the installed system nifti_clib plus a stand-in for ITKznz: nifti_image_read returns NULL for a file it reads correctly when the same ITKznz is present but the nifti side is mangled. So the earlier claim that a shared system nifti was safe was wrong; shared is exactly the case that breaks, and it breaks silently. Since a prebuilt library cannot be renamed and ITK 4.x has no ITK_USE_SYSTEM_NIFTI to point it at one copy, refuse the combination instead of documenting it. USE_SYSTEM_NIFTI remains available for builds without the ITK tools, which is where a distribution package would want it: MT_BUILD_ITK_TOOLS=OFF or MT_BUILD_LITE=ON. Verified all four combinations -- only the unsafe one is rejected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
My earlier justification overstated the consequence. It said the system libniftiio.so ends up returning NULL for valid files; that came from a stand-in for ITKznz which returned NULL, not from ITK's own code. Rebuilt against ITK release-4.14's actual znzlib sources: the interposition is real and measurable -- 8 znz names land in the executable's dynamic symbol table -- but ITK's implementation and nifti_clib's agree closely enough on LP64 that reads still succeed. Both are built with HAVE_ZLIB, so sizeof(struct znzptr) matches too. What remains is still worth refusing: it is one definition of each function silently replacing another, it breaks on ILP32 with large-file support (ITK's znzseek/znztell use long, nifti_clib 3.x uses znz_off_t -- 4 bytes against 8), and it breaks wherever HAVE_ZLIB differs between the two builds. So the guard stays, with an accurate rationale rather than an alarming one. Also note where the real fix belongs: ITK's znzlib needs the mangle header its niftiio already has, after which this guard can be dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
InsightSoftwareConsortium/ITK#6756 is merged, so ITK's bundled znzlib now carries the itk_* prefixing its niftiio has had since 2017. That was the one thing making a system NIfTI unusable alongside the ITK tools: ITKznz exported plain znzopen/znzread/znzseek/..., which interpose over any other znzlib in the same binary, and every tool linking both ITK and libminc has one. Pin moves cae3eb95 -> 6073b968. Those are the only two commits in between -- the fix and its merge -- so this bump carries nothing else. With both halves of ITK's copy prefixed, USE_SYSTEM_NIFTI and MT_BUILD_ITK_TOOLS no longer collide, and the FATAL_ERROR refusing the combination goes away. Verified against the new tarball: ITK's znzlib.c builds to 15 itk_* exports and no unmangled ones, and a program linking it next to a shared system nifti_clib exports no znz names at all in its dynamic symbol table, where the old pin put 8. All four USE_SYSTEM_NIFTI x MT_BUILD_ITK_TOOLS combinations now configure, including the one that was previously rejected. Our own NIfTI stays renamed to minc_* regardless; ITK still has no ITK_USE_SYSTEM_NIFTI, so the two builds remain separate copies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa
20dee19 to
f8df3fa
Compare
|
Rebased onto The README commits this branch depended on landed via #227, so nothing README-related remains here. Re-verified on the new base, which matters because
On the macOS CI failures: they are pre-existing and not from this branch. That is the N3 (Unrelated, but noticed while checking: the |
|
@vfonov ITK does not accept a system NIfTI — there is no
option(ITK_USE_SYSTEM_ZLIB "Use system-installed zlib" ${ITK_USE_SYSTEM_LIBRARIES})
if(ITK_USE_SYSTEM_ZLIB)
find_package(ZLIB REQUIRED)
set(ITKZLIB_LIBRARIES "ZLIB::ZLIB")
set(ITKNIFTI_LIBRARIES ITKznz ITKniftiio)
itk_module_impl()So there are always two NIfTI copies in any binary linking both ITK and libminc. That said, your instinct is right that something changed. Since InsightSoftwareConsortium/ITK#6756 the pinned ITK prefixes both halves of its copy — Two reasons I would keep it anyway, though I am happy to be overruled:
Dropping it while pinned to 4.14 is a defensible call; it would just want a comment saying it depends on the ITK pin carrying #6756, so an ITK upgrade re-examines it. Say the word and I will strip the mangling, the patch step and the two tests out of BIC-MNI/libminc#152. |
Why
nifti_clibupstream (NIFTI-Imaging) has not moved since v3.0.0 in July 2020. The ITK fork is the maintained line, so this pins that instead. It carries no tags, so the pin is a commit SHA (f24a607, 2026-07-22).The bump matters beyond freshness: the fork exports three symbols v3.0.0 did not —
nifti_image_write_status,nifti_image_write_bricks_status,nifti_set_fix_floats. The existingnifti_mangle.hleft those unprefixed, which is the exact condition it exists to prevent: an unmangled symbol can collide at link time with theniftiioITK bundles (ITK 4.x here has noITK_USE_SYSTEM_NIFTIswitch).What
cmake-modules/BuildNIFTI.cmakeInsightSoftwareConsortium/nifti_clib@f24a607, in step with the identical pin in the libminc submodule.NIFTI::niftiio/NIFTI::znzimported targets thatnifti_clib's ownNIFTIConfig.cmakeprovides, so theADD_SUBDIRECTORY()'d consumers (libminc, minctools, Display) link the same way whether NIFTI came from here or fromfind_package(NIFTI CONFIG). The real config package cannot be used here — ExternalProject builds at build time, long after those consumers configure — so this mirrors it. Build ordering still rides on the existingadd_dependencies(... NIFTI)calls, not on the imported targets.USE_SYSTEM_NIFTIis now a realoption(). It had been commented out for years, leavingFIND_PACKAGE(NIFTI REQUIRED)unreachable; it is restored as a proper option so it shows up inccmakealongside the otherUSE_SYSTEM_*switches. It selectsfind_package(NIFTI CONFIG REQUIRED)overbuild_nifti(), and needs no consumer changes — both paths supply the same two imported targets.LIBMINC_USE_SYSTEM_NIFTIis forwarded so libminc can skip its mangling invariant, and NIFTI drops out ofMINC2_depsin system mode since there is no ExternalProject to wait for.The mangling caveat is documented at the call site rather than treated as a reason to drop the option: the bundled build is renamed to
minc_*so it cannot collide with ITK's bundledITKniftiio, whereas a system nifti is unmangled. That is fine against the sharedlibniftiio.sodistributions package — which is what the release.deb/.rpmlink — but not against a static system nifti withMT_BUILD_ITK_TOOLSon.cmake-modules/FindNIFTI.cmake— deleted.nifti_clibships a real config package, and the hand-rolled module hardcoded/usr/local/bicand reported everything it found as "NetCDF headers"/"NetCDF library".Verification
Against a genuine installed system
nifti_clib(shared, unmangled, exportingNIFTI::niftiio):USE_SYSTEM_NIFTI=ON— configures and generates;mnc2nii/nii2mnclink/usr/lib/libniftiio.so.2.1.0and/usr/lib/libznz.so.3.0.0; no NIFTI ExternalProject is generated; the exportedLIBMINCConfigrecords the system library.USE_SYSTEM_NIFTI=OFF— unchanged; consumers link the stagedlibniftiio.a/libznz.aand pick up the nifti include directory as-isystem.minc_-prefixed, with zero overlap against an unmangled build of the same source.Ordering
Companion PRs, all against
develop-1.9.18:nifti_mangle.h, deletes a dead vendorednifti/copy, skips the mangling test in system mode. Must land with this one:PATCH_COMMANDhere readsnifti_mangle.hout of that submodule, so a new pin with the old mangle list leaves those three symbols exposed.NIFTI::niftiio. Its pin should not move before this merges, or the target is undefined andmnc2nii/nii2mncare silently skipped.GIFTI_FOUNDguards. Independent, since GIFTI is disabled here.NIFTI_ROOTguessing withfind_package(NIFTI CONFIG). Independent;BuildBEAST.cmakepassesUSE_NIFTI=OFF.Submodule pin bumps are deliberately not included here.
Not in scope
README.md's options table documentsUSE_SYSTEM_NIFTI; that table lives in the in-flight Rewrite README: correct install instructions, document what ships #227, where the entry needs its caveat updated.INSTALL_COMMAND $(MAKE)inBuildNIFTI.cmakeblocks the Ninja generator, but all 24Build*.cmakemodules do the same, so fixing one changes nothing.🤖 Generated with Claude Code
https://claude.ai/code/session_019katBUnczdvUS7WQ4RpVoa