Repository navigation
BUG: Keep the Pixel Spacing row/column swap when spacing is incomplete - #7066
Merged
hjmjohnson merged 1 commit intoNov 1, 2026
Merged
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
Pixel Spacing (0028,0030) is row spacing then column spacing (DICOM PS3.3 Section 10.7.1.3), the reverse of the index order DCMTKFileReader::GetSpacing returns, so it swaps the two values. When fewer than three spacings were found, the function overwrote spacing[0] and spacing[1] with the unswapped values before returning EXIT_FAILURE. Spacing Between Slices (0018,0088) is absent from most single-frame series and DCMTKImageIO does not check the return value, so those series were read with their in-plane axes transposed. Square pixels hide the effect. Values that were found are already stored in the returned order, and the rest keep the 1.0 they were initialized with.
pieper
force-pushed
the
dcmtk-pixel-spacing-order
branch
from
November 1, 2026 16:39
09a1e7c to
0f9f9b4
Compare
hjmjohnson
approved these changes
Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DCMTKFileReader::GetSpacingswaps Pixel Spacing into the index order it returns, then overwrites that with the unswapped values when it cannot find all three. Series lacking Spacing Between Slices (0018,0088) read with in-plane axes transposed.Measured behavior
Single-frame CT,
Rows 4,Columns 8,PixelSpacing 1\3, so the correct ITK spacing is[3, 1, …]. Read through 3D Slicer 5.12.4, which exposes both back ends:DCMTKImageIO[1, 3, 2]— swapped[3, 1, 2]GDCMImageIO[3, 1, 2][3, 1, 2]DCMTKImageIO::ReadImageInformationdoes not check theEXIT_FAILUREthatGetSpacingreturns on that path. Square pixels hide the effect, which is why it has gone unnoticed.Why the swap is correct
DICOM PS3.3 Section 10.7.1.3:
Other implementations agree, several of them with a comment saying so:
libsrc/src/dctool/pixposn.cc— "PixelSpacing Row/Col is space BETWEEN rows/cols respectively - hence row pixels of size PixelSpacingRow along ImageOrientationPatientCol"com/pixelmed/geometry/GeometryOfSlice.java—row*columnArray[i]*voxelSpacing[0]/*between rows*/ + column*rowArray[i]*voxelSpacing[1]/*between cols*/gdcmImageHelper.cxx—sp.push_back(at.GetValue(1)); sp.push_back(at.GetValue(0));, andstd::swap(sp[0], sp[1])on the general pathdcmimgle/libsrc/diimage.cc—PixelHeightfrom value 1,PixelWidthfrom value 2src/highdicom/spatial.py—spacing_between_rows/spacing_between_columns, each paired with the matching index directionnicom/dicomwrappers.py—np.fliplr(iop)withvoxel_sizes = (ps[0], ps[1], zs)libsrc/Itk2DicomConverter.cpp— writesspacing[1] \ spacing[0]from an ITK imageThe two comments describing (0028,0030) as "physical X,Y spacing" are corrected in the same commit; they state the value order the opposite way round and are what the removed assignments were consistent with.
Test
itkDCMTKImageIOGTest.cxxadds a GoogleTest driver to this module.DCMTKImageIO.InPlaneSpacingOrderWithoutSpacingBetweenSliceswrites a single-frame CT withPixelSpacing 1\3and no (0018,0088), reads it back, and checks the spacing is[3, 1]. It writes its own input with DCMTK, already inTEST_DEPENDS, so no new ExternalData is needed.Not built locally.
ITKIODCMTKisEXCLUDE_FROM_DEFAULTandconfigure-cidoes not setModule_ITKIODCMTK, so the Pixi jobs appear not to build this module — a reviewer with the module enabled would be confirming something CI does not. The behavior in the table above was measured against a built ITK, and the fix itself is a deletion of three assignments.AI assistance
GetSpacingby differential testing of the two ImageIO back ends with and without (0018,0088), then surveyed how other DICOM implementations order the attributeRelated: pydicom/pydicom#2416 — the same confusion in an example, found in the same survey.