Skip to content

ENH: Ingest ITKIOTransformDCMTK into Modules/IO#6310

Merged
dzenanz merged 83 commits into
InsightSoftwareConsortium:mainfrom
hjmjohnson:ingest-IOTransformDCMTK
May 25, 2026
Merged

ENH: Ingest ITKIOTransformDCMTK into Modules/IO#6310
dzenanz merged 83 commits into
InsightSoftwareConsortium:mainfrom
hjmjohnson:ingest-IOTransformDCMTK

Conversation

@hjmjohnson
Copy link
Copy Markdown
Member

Ingests ITKIOTransformDCMTK (DICOM Spatial-Registration reader, DCMTK-based) from the standalone remote module into Modules/IO/IOTransformDCMTK/ using the v4 merge-topology-preserving pipeline. EXCLUDE_FROM_DEFAULT, hard DEPENDS ITKDCMTK. Closes the migrated test-improvement work in #6308 and surfaces a follow-up audit in #6309.

Ingest provenance & topology
  • Upstream tip ingested: 9d7f4a56 (2026-04-23).
  • Pipeline: Utilities/Maintenance/RemoteModuleIngest/ingest-module-v4.sh IOTransformDCMTK IO (filter-repo whitelist + scaffolding deny-pass + per-commit sanitize-history.py + Mode-A --no-ff --allow-unrelated-histories merge).
  • Merge topology: 13 merge commits preserved end-to-end (git rev-list --count --merges upstream/main..HEAD).
  • The Modules/Remote/IOTransformDCMTK.remote.cmake configure-time fetch is removed in the same PR.
Why EXCLUDE_FROM_DEFAULT + DEPENDS ITKDCMTK

IOTransformDCMTK requires the DCMTK third-party library (Module_ITKDCMTK). The module's itk-module.cmake declares DEPENDS ITKDCMTK and EXCLUDE_FROM_DEFAULT; the readonly test-only deps (ITKIODCMTK, ITKIOGDCM, ITKImageGrid, ITKTestKernel) cover both the DCMTK transform-read path and the GDCM-based example driver. Enabling the module is opt-in:

-DModule_ITKDCMTK:BOOL=ON
-DModule_IOTransformDCMTK:BOOL=ON

pyproject.toml's configure-ci task is updated to set both, so at least one CDash nightly configuration exercises the module post-merge.

Review-driven fixes on top of the raw ingest

The raw upstream module was a 2-star compliance level. A pre-PR greptile-style review surfaced ten concrete issues; the following are addressed before this PR opens (commit hashes are on the branch):

Commit Prefix What
6e1b61db46 COMP Switch upstream's dual-mode find_package(ITK) CMakeLists to in-tree itk_module_impl() + itk_module_add_library
2dab47c240 DOC Add README.md documenting in-tree provenance
54f09020a6 DOC Replace \todo Detailed description. in class doxygen with SRO + matrix-type behavior
b7f470d596 DOC Replace placeholder itk-module.cmake DESCRIPTION
b2d99d5e3e BUG Null-check DCMTK getDataset()/getItem() returns; drop dead break after itkExceptionMacro (which throws); fix argc < 5 off-by-one (argv[5] accessed)
b55645c8e9 ENH Implement PrintSelf on the templated IO + the factory (was empty, missing Superclass::PrintSelf)
cc5ac212a4 COMP Wrap DCMTKTransformIO as templated class for D and F instead of itk_wrap_simple_class (was wrong for ITK_WRAP_PYTHON)
c56af3a5b8 BUG Force LC_NUMERIC=C via itk::NumericLocale RAII for std::stod matrix parsing
4b5dd2707d COMP Enable Module_IOTransformDCMTK/Module_ITKDCMTK in pyproject.toml configure-ci
1ff3c0a035 STYLE Match ITK actual-then-expected convention in ITK_TEST_EXPECT_EQUAL + name fixture in comment
a92e2aec52 STYLE Replace OFString::compare(...) == 0 with operator==
2ce6713f18 STYLE Drop orphan explicit-instantiation reminder comment
ITK#6308 — test improvements (migrated from ITKIOTransformDCMTK#5)

Migrated the upstream test-improvement issue ITKIOTransformDCMTK#5 (originally opened by @jhlegarreta) to ITK as #6308 and addressed three of four bullets in this PR:

  • f77c0a9187itkDCMTKTransformIOTest uses _STATUS_VALUE macros + testStatus accumulator; adds ITK_TEST_SET_GET_VALUE for FrameOfReferenceUID.
  • 7944060ee0ReadDicomTransformAndResampleExample.cxx refactored to ITK_TRY_EXPECT_NO_EXCEPTION + ITK_TEST_EXPECT_TRUE_STATUS_VALUE; commented-out DCMTKImageIO/DCMTKSeriesFileNames blocks dropped. (Surfaced a SmartPointer-lifetime bug caught only via the image-baseline --compare step.)
  • 94f421a776 — Rename ReadDicomTransformAndResampleExample.cxxitkDCMTKTransformIOResampleTest.cxx (file + function + ctest name) to match ITK convention.

The remaining bullet (status-accumulating exception macros) requires changes to ITKTestKernel — out of scope here; left open in #6308.

Related followup — ITK#6309

Filed ITK#6309 to audit the GDCM→DCMTK fallback in the example test. The original "DCMTKImageIO does not populate the MetaDataDictionary yet" comment from upstream is now stale (resolved by 54887f7e9b + follow-ups), but the example still uses GDCMImageIO/GDCMSeriesFileNames to read the FoR UID. Swapping to DCMTK* requires API-parity verification on DCMTKSeriesFileNames and a baseline-compare check.

Local verification (build-standard, macOS arm64)
cmake -DModule_ITKDCMTK:BOOL=ON -DModule_IOTransformDCMTK:BOOL=ON build-standard
ninja -j10 IOTransformDCMTK-all       # clean
ctest -R IOTransformDCMTK -j8         # 4/4 pass:
   IOTransformDCMTKKWStyleTest        ✓
   IOTransformDCMTKInDoxygenGroup     ✓
   itkDCMTKTransformIOTest            ✓ (status-accumulating, fixture-bound)
   itkDCMTKTransformIOResampleTest    ✓ (baseline-compared)
pre-commit run --all-files            # all hooks pass
Post-merge follow-up actions

After this PR merges, run Phase B:

Utilities/Maintenance/RemoteModuleIngest/archive-remote-module.sh IOTransformDCMTK

Phase B publishes the upstream removal commit + flips archived=true on InsightSoftwareConsortium/ITKIOTransformDCMTK. Per the ingest-archive-readme rule, the archived repo's GitHub-rendered README must be promoted from MIGRATION_README.md to README.md (with the original README.rstinfo.rst).

thewtex and others added 30 commits August 15, 2014 00:07
This commit updates CMakeLists.txt to ensure built library
is shared.

This commit introduces explicit instantiation where
templated factories are explicitly instantiated for "double" and "float"
types.

This will allow templated factories to be registered in one translation
unit and instantiated in an other.

See http://stackoverflow.com/questions/8024010/why-do-template-class-functions-have-to-be-declared-in-the-same-translation-unit

See ITK issue InsightSoftwareConsortium#3393
https://issues.itk.org/jira/browse/ITK-3393
This commit updates explicit instantiation to use approach similar
to what was done in InsightSoftwareConsortium/ITK@b72726c (BUG: Explicitly
instantiate common MetaDataObjects).

While "extern" is only part of the c++ standard starting with c++11 (see
section 14.7.2 of the standard), it is supported by Visual Studio 2008,
GCC and Clang using compiler specific extension in earlier version:

* Visual Studio 2008: https://msdn.microsoft.com/en-us/library/by56e477(v=vs.90).aspx

* Gcc: https://gcc.gnu.org/onlinedocs/gcc-4.8.4/gcc/Template-Instantiation.html#Template-Instantiation

* Clang (since v2.9): http://clang.llvm.org/cxx_status.html

For completeness, initial draft of the "extern" keyword were first
introduced in 2003 [1] and then revised in 2006 [2], and ultimately
standardized in c++11 [3]:

[1] N1448: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1448.pdf
[2] N1987: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1987.htm
[3] N3242: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/

See ITK issue InsightSoftwareConsortium#3393
https://issues.itk.org/jira/browse/ITK-3393

Suggested-by: Bradley Lowekamp <blowekamp@mail.nih.gov>
This commit fixes warnings introduced in InsightSoftwareConsortium/ITK@eb2316a
(BUG: TransformIO: Implement explicit template instantiation using "extern".)
by handling version of GCC not supporting "pragma diagnostic"
preprocessor instruction.

It does so by using the new ITK macros introduced in ITK
commit (COMP: TransformIO: Handle compiler not supporting "pragma
diagnostic".)
COMP: Handle compiler not supporting "pragma diagnostic".
This commit fixes the approach introduced in InsightSoftwareConsortium/ITK@eb2316a
(BUG: TransformIO: Implement explicit template instantiation using "extern".).
By moving the use of custom EXPORT_EXPLICIT macro *OUTSIDE* the header guards,
it ensures the defined and undefined states of ITK_TEMPLATE_EXPLICIT_<classname>
macro are considered within the same compilation unit.
Must also update / improve the library link specification to avoid linker
errors.
…on-approa

ch

3393 update explicit instantiation approach
Correction of bug ITK-3459 [1]. EXERCISE_BASIC_OBJECT_METHODS now requires
3 arguments instead of 2. The new argument is the Superclass name.

[1] https://issues.itk.org/jira/browse/ITK-3459
hjmjohnson and others added 20 commits May 23, 2026 13:44
Brings IOTransformDCMTK from a configure-time remote fetch into the ITK
source tree at Modules/IO/IOTransformDCMTK/ using the v4 ingestion
pipeline (whitelist filter-repo + per-commit clang-format + black +
commit-prefix sanitization).

Upstream repo:  https://github.com/InsightSoftwareConsortium/ITKIOTransformDCMTK.git
Upstream tip:   acc3378f1d5207ae5bbc8d590616ceb70fe3c1c9
Ingest date:    2026-05-20
Whitelist:      default.list

Per-commit transforms applied across all 61 commits:
  - filter-repo --paths-from-file (whitelist)
  - filter-repo --to-subdirectory-filter Modules/IO/IOTransformDCMTK
  - clang-format -style=file (ITK main's .clang-format) for *.cxx/.h/.hxx/...
  - black for *.py
  - heuristic ITK prefix added to commit subjects without one

Merge topology preserved: 17 -> 12 merge(s).

Primary author: Matt McCormick <matt.mccormick@kitware.com>

Co-authored-by: Dženan Zukić <dzenan.zukic@kitware.com>
Co-authored-by: Francois Budin <francois.budin@gmail.com>
Co-authored-by: Hans J. Johnson <hans-johnson@uiowa.edu>
Co-authored-by: Hans Johnson <hans-johnson@uiowa.edu>
Co-authored-by: Hans Johnson <hans.j.johnson@gmail.com>
Co-authored-by: Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>
Co-authored-by: Jon Haitz Legarreta <jhlegarreta@vicomtech.org>
Co-authored-by: Jon Haitz Legarreta Gorroño <jon.haitz.legarreta@gmail.com>
Co-authored-by: Mathew Seng <mathewseng@gmail.com>
Co-authored-by: Matt McCormick <matt@mmmccormick.com>
Co-authored-by: Tom Birdsong <tom.birdsong@kitware.com>
Co-authored-by: Zach Williamson <zachary-williamson@uiowa.edu>
Modules/IO/IOTransformDCMTK is EXCLUDE_FROM_DEFAULT and requires
Module_ITKDCMTK. README documents the gating and ingest provenance.

Also remove Modules/Remote/IOTransformDCMTK.remote.cmake; the module
is now in-tree and the configure-time fetch is obsolete.
Upstream IOTransformDCMTK CMakeLists used the dual-mode external-build
boilerplate (find_package(ITK) fallback) and add_library + manual
itk_module_target. As an in-tree module, the standard
itk_module_add_library macro propagates include dirs and link deps
automatically (matches MGHIO and other in-tree IO modules).
itk-module.cmake DESCRIPTION was a placeholder string; replace with a
one-line summary of the module's purpose (DICOM Spatial-Registration
reading via DCMTK).

DCMTKTransformIO class doxygen had a bare \todo; replace with a brief
description of which DICOM SOP class is recognized, how the matrix
type drives transform-class selection, and the role of the
FrameOfReferenceUID filter.
The ingested upstream description was a placeholder string. Replace
with a one-line summary of what the module reads (DICOM
Spatial-Registration Objects).
- Guard fileFormat.getDataset() and three getItem() return values
  against nullptr before dereferencing; DCMTK returns nullptr on
  malformed sequences.
- Remove dead 'break' after itkExceptionMacro (which throws); the
  break is unreachable.
- ReadDicomTransformAndResampleExample required 5 positional args
  plus argv[0] but checked argc < 5; reading argv[5] with the minimal
  6-arg invocation would have read past the array bound. Tighten to
  argc < 6.
DCMTKTransformIOFactory::PrintSelf was empty and did not chain to
Superclass; replace with a Superclass-only forward (the factory holds
no extra state). Also normalize the stray 'Factory ::' spacing.

DCMTKTransformIO declared no PrintSelf override despite carrying
m_FrameOfReferenceUID state; add a declaration + implementation that
prints the desired Frame-of-Reference UID after the base class print.
The upstream .wrap used itk_wrap_simple_class, which targets a
non-templated class. DCMTKTransformIO is templated on the internal
computation value type; mirror the HDF5TransformIO wrapping pattern
(itk_wrap_class + itk_wrap_template for D and F) so ITK_WRAP_PYTHON
builds resolve the symbol.
DICOM Decimal-String values always use '.' as decimal separator; on a
comma-locale host (e.g. de_DE), std::stod would truncate matrix
entries at the first '.'. Wrap the Read() body in an itk::NumericLocale
RAII scope guard (thread-safe via uselocale/_configthreadlocale per
platform) so the parse is locale-independent. Mirrors the
NumericLocale usage in itkNrrdImageIO.
…QUAL

ITK convention across Modules/IO/*/test/ puts the observed value
first and the expected value second in ITK_TEST_EXPECT_EQUAL. Two
size() comparisons in itkDCMTKTransformIOTest had the literal
expected count first; swap. Also add a brief comment naming the
fixture's MatrixRegistration structure so the literal counts read
self-explanatorily.
OFString provides an operator==(const char *) overload; replace the
explicit .compare(...) == 0 form with the natural equality
comparison.
The orphan comment sat between the closing namespace and the header
guard #endif, separated from the actual extern template declarations
it described by the include guard boundary. The instantiations below
are self-evident.
IOTransformDCMTK is EXCLUDE_FROM_DEFAULT and gated on Module_ITKDCMTK,
so without an explicit opt-in no CDash dashboard would compile or test
it post-ingest, leaving the in-tree module without coverage. Enable
both modules in pyproject.toml's configure-ci task so the test driver
runs on at least one nightly configuration.
Addresses ITK#6308 (migrated from upstream ITKIOTransformDCMTK#5):

- Replace early-return ITK_TEST_EXPECT_TRUE/EQUAL with the
  _STATUS_VALUE variants so all checks run on every invocation.
- Add ITK_TEST_SET_GET_VALUE coverage for FrameOfReferenceUID before
  and after Set, exercising the macro pair.
- Use 'auto' for SmartPointer locals to remove explicit ::Pointer
  noise.
- Replace IsNull() with explicit nullptr comparison (raw pointer from
  dynamic_cast).
- Print 'Test finished.' before returning testStatus, matching the
  ITK testing convention.
Replace per-step raw try/catch + return EXIT_FAILURE blocks with
ITK_TRY_EXPECT_NO_EXCEPTION; replace if(!ExposeMetaData) early
returns with ITK_TEST_EXPECT_TRUE_STATUS_VALUE accumulating into
testStatus. Drop the commented-out DCMTKImageIO and
DCMTKSeriesFileNames type aliases (GDCM is the only path; the
MetaDataDictionary-population gap for DCMTKImageIO is a separate
concern). Use 'auto' uniformly for SmartPointer locals.
Rename ReadDicomTransformAndResampleExample.cxx ->
itkDCMTKTransformIOResampleTest.cxx and rename the entry-point
function and ctest name to match. Aligns with ITK's
itk<TypeUnderTest>Test.cxx convention.
DCMTK is a transitive dependency of IOTransformDCMTK; users only
need to set Module_IOTransformDCMTK=ON. Mentioning Module_ITKDCMTK
explicitly in the enable example confuses readers about which knob
is doing the work.
The override only called Superclass::PrintSelf(os, indent), which is
exactly what happens by default when no override is declared. Removing
it eliminates dead-code noise without changing print behavior.
Drop the explicit WRAPPER_AUTO_INCLUDE_HEADERS=OFF guard and the
hand-written itk_wrap_include("itkDCMTKTransformIO.h"). The class
name itk::DCMTKTransformIO maps to the canonical itkDCMTKTransformIO.h
header via the default name-derivation rule, so no override is
needed here. Precedents like HDF5TransformIO keep the override only
because they wrap two classes that share a header.
Two functional defects in DCMTKTransformIO::Read() flagged in greptile
review of InsightSoftwareConsortium#6310:

1. RIGID_SCALE matrices were stored in a ScaleTransform via SetMatrix.
   ScaleTransform models axis-aligned scaling only; its inherited
   MatrixOffsetTransformBase::SetMatrix path writes the matrix without
   updating m_Scale, so GetParameters() returns stale defaults that do
   not describe the rotation+isotropic-scale matrix in the file.
   Spatial mapping was correct via raw-matrix application but parameter
   inspection and serialization were wrong.

   DICOM RIGID_SCALE is rotation + isotropic scale + translation
   (7 DOF). Use Similarity3DTransform, whose SetMatrix(m, tol) recovers
   the isotropic scale as cbrt(det(m)) and the rotation via polar
   decomposition.

2. When DCM_FrameOfReferenceTransformationMatrixType was absent the
   fully-parsed matrix was silently discarded with no exception and no
   warning, returning a CompositeTransform with no children. Throw
   instead, naming the offending matrix-sequence index.

The unused OFString seriesNumber local in CanReadFile and the unused
itkVersorTransform.h include are removed in the same commit; the new
RIGID_SCALE path needs itkSimilarity3DTransform.h instead.

A new GoogleTest-style unit test
itkDCMTKTransformIORigidScaleRoundTripTest verifies the polar-
decomposition path: given a known versor + isotropic scale +
translation, build the equivalent rotation*scale matrix as an
AffineTransform (mimicking what Read() assembles from the DICOM matrix
entries), apply the same Similarity3DTransform::SetMatrix + SetOffset
calls the IO now uses, and confirm versor, scale, translation, and
mapped point coordinates all round-trip within 1e-10.
@hjmjohnson hjmjohnson force-pushed the ingest-IOTransformDCMTK branch from 596964a to 1ab5384 Compare May 23, 2026 18:46
@hjmjohnson
Copy link
Copy Markdown
Member Author

Windows Pixi failure is a DCMTK/MSVC __cplusplus build-flag bug, not an unsupported platform. Enabling Module_ITKDCMTK on the Windows Pixi leg exposes a latent issue: DCMTK's osconfig.h C++11 guard fires under MSVC because /Zc:__cplusplus is never passed. Fix is a small COMP change (likely best as its own PR ahead of this ingest).

Symptom

Pixi-Cxx (windows-2022) — 25 DCMTK tests report (Not Run) (all ITKIODCMTK + the new IOTransformDCMTK tests). macOS/Ubuntu pass. Root cause is a compile failure:

dcmtk/config/osconfig.h(938): fatal error C1189: #error: DCMTK was configured
to use C++11 features, but your compiler does not or was not configured to provide them.

The ITKIODCMTK/IOTransformDCMTK libraries fail to compile → their test drivers never link → CTest marks every dependent test Not Run.

Root cause

DCMTK is built with -DDCMTK_ENABLE_CXX11:BOOL=ON (Modules/ThirdParty/DCMTK/CMakeLists.txt), which bakes a __cplusplus >= 201103L guard into the generated osconfig.h. MSVC reports __cplusplus == 199711L unless /Zc:__cplusplus is passed — and that flag appears nowhere in the ITK tree. So the consumer compile of itkDCMTKImageIO.cxx (etc.) trips the #error.

This is the well-known MSVC __cplusplus-reporting pitfall, not an unsupported platform: ITK already carries dedicated MSVC/WIN32 DCMTK handling (DCMTK_OVERWRITE_WIN32_COMPILER_FLAGS=OFF, CMAKE_MSVC_RUNTIME_LIBRARY) and multi-config (Visual Studio) DCMTK target fixes in history. It was simply latent because ITKIODCMTK is EXCLUDE_FROM_DEFAULT and the default CI never built it on the Windows Pixi (Ninja + MSVC + -std:c++17) toolchain until this PR enabled it.

Recommended fix

Ensure /Zc:__cplusplus is set for the MSVC compile of DCMTK consumers and propagated transitively (it must be active where osconfig.h is included by ITKIODCMTK):

# Modules/IO/DCMTK/src/CMakeLists.txt
target_compile_options(ITKIODCMTK PUBLIC
  $<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>)

PUBLIC so downstream consumers (IOTransformDCMTK) inherit it. Alternative (less preferred): build DCMTK with DCMTK_ENABLE_CXX11=OFF. Best handled as a standalone COMP PR in the ITKDCMTK ThirdParty/IO module ahead of this ingest, since it benefits anyone enabling DCMTK on Windows.

DCMTK's generated osconfig.h guards C++11/14/17 on the __cplusplus
macro. MSVC reports 199711L unless /Zc:__cplusplus is passed, so every
DCMTK consumer fails with C1189 even when built as C++17. DCMTK sets
the flag for its own build but does not export it to consumers.

Attach it to the ITKDCMTKModule INTERFACE so all consumers inherit it.
@github-actions github-actions Bot added the area:ThirdParty Issues affecting the ThirdParty module label May 24, 2026
ITKIODCMTK public headers include DCMTK headers, so ITKDCMTK is an
interface-level dependency. Declaring it public (not PRIVATE_DEPENDS)
propagates ITKDCMTKModule usage requirements -- notably the MSVC
/Zc:__cplusplus flag -- to the module's test and header-test targets,
which otherwise fail with C1189 from osconfig.h.
@hjmjohnson hjmjohnson requested a review from dzenanz May 24, 2026 20:54
@dzenanz dzenanz merged commit f117adb into InsightSoftwareConsortium:main May 25, 2026
19 of 20 checks passed
hjmjohnson added a commit to InsightSoftwareConsortium/ITKIOTransformDCMTK that referenced this pull request May 27, 2026
The IOTransformDCMTK module has been ingested into ITK main under Modules/IO/IOTransformDCMTK via
InsightSoftwareConsortium/ITK#6310. Delete the whitelisted module
sources, preserve the original README, and promote the migration
notice to README.md so the GitHub landing page reflects archived
status.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:IO Issues affecting the IO module area:Python wrapping Python bindings for a class area:Remotes Issues affecting the Remote module area:ThirdParty Issues affecting the ThirdParty module type:Enhancement Improvement of existing methods or implementation type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants