Skip to content

ENH: Use NumberToString for lossless float metadata in NIfTI reader#6003

Merged
hjmjohnson merged 7 commits intoInsightSoftwareConsortium:mainfrom
hjmjohnson:fix-nifti-lossless-metadata
Apr 9, 2026
Merged

ENH: Use NumberToString for lossless float metadata in NIfTI reader#6003
hjmjohnson merged 7 commits intoInsightSoftwareConsortium:mainfrom
hjmjohnson:fix-nifti-lossless-metadata

Conversation

@hjmjohnson
Copy link
Copy Markdown
Member

@hjmjohnson hjmjohnson commented Apr 1, 2026

Summary

Fixes float/double precision loss in ITK persistence-layer serialization (ITK issue #3249).

When ITK IO modules write numeric metadata to text-based format headers using default std::ostream precision (6 significant digits), values like 1.2345678901234568 are truncated to "1.23457", which parses back to a different bit pattern. This PR applies itk::ConvertNumberToString() (shortest exact round-trip string via Google double-conversion) consistently across 6 IO modules.

Files Fixed

Module File Code Path Fixed
NIfTI itkNiftiImageIO.cxx Read path: pixdim[], intent_p1/2/3, scl_slope/inter, cal_max/min, slice_duration → MetaDataDictionary strings
MetaImage itkMetaImageIO.cxx + .h Write path: scalar double/float keys, vector<double> loop, WriteMatrixInMetaData<> template
NRRD itkNrrdImageIO.cxx Write path: explicit float/double specializations of _dump_metadata_to_stream<T>
PolygonGroupSpatialObject XML itkPolygonGroupSpatialObjectXMLFile.cxx Write path: 3D polygon vertex coordinates (Point<double,3>)
FreeSurfer ASCII Mesh itkFreeSurferAsciiMeshIO.h Write path: WritePoints<T> loop template
VoxBoCUB itkVoxBoCUBImageIO.cxx Write path: m_Spacing[0/1/2] in CUB text header

Files Verified Correct (No Fix Needed)

  • itkNrrdImageIO.cxxArray<double/float> already uses ConvertNumberToString via itkArrayOutputSpecialization.cxx
  • itkVoxBoCUBImageIO.cxx lines 682-684 — origin deliberately cast to int (CUB stores integer voxel coordinates)

Test Coverage

Adds a new commit (ENH: Add precision round-trip tests) with 4 precision regression tests:

  • NIfTI (itkNiftiImageIOTest3.cxx): TestNiftiFloatMetadataPrecision() — writes image with 0.12345679-mm spacing, reads back, verifies MetaDataDictionary["pixdim[1]"] parses to the exact float32 bit pattern
  • MetaImage (itkMetaImageIOMetaDataTest.cxx): encapsulates hpDouble=1.2345678901234568 and hpFloat=1.2345679f, writes/reads .mha, checks exact equality via stod/stof (bypasses the existing loose 1e-6 tolerance)
  • NRRD (itkNrrdMetaDataTest.cxx): same pattern for native double and float NRRD metadata keys
  • VoxBoCUB (itkVoxBoCUBImageIOTest.cxx): creates image with 0.123456789012345 spacing, writes/reads CUB, asserts GetSpacing()[0] == original double

All 4 tests FAIL against the unfixed IO code and PASS with this PR applied.

Root Cause

Default std::ostream precision is 6 significant digits. Float needs ≥8 and double needs ≥17 for lossless serialization. itk::ConvertNumberToString() (wrapping double-conversion::ToShortest()) produces the shortest decimal string that round-trips exactly.

Closes #3249

Test Plan

  • itkNiftiVectorImageTest — includes TestNiftiFloatMetadataPrecision
  • itkMetaImageIOMetaDataTest
  • itkNrrdMetaDataTest
  • itkVoxBoCUBImageIOTest1
  • CI green on CDash

@github-actions github-actions bot added type:Enhancement Improvement of existing methods or implementation area:IO Issues affecting the IO module labels Apr 1, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 1, 2026

Greptile Summary

This PR systematically fixes floating-point precision loss in six ITK IO modules by replacing default std::ostream serialization (6 significant digits) with itk::ConvertNumberToString(), which produces the shortest decimal string guaranteed to round-trip exactly. The fix is correct and well-motivated, addresses a long-standing data-fidelity issue (#3249), and includes four targeted regression tests that verify bit-exact round-trips for both float and double.

Key changes:

  • NIfTI (itkNiftiImageIO.cxx): Converts pixdim[], quaternion parameters, srow matrix, intent parameters, slope/intercept, and cal/toffset metadata to use ConvertNumberToString. The srow loop refactor is clean and correct.
  • MetaImage (itkMetaImageIO.cxx/.h): Fixes scalar double/float keys, vector<double> loop, and the WriteMatrixInMetaData<> template in the header.
  • NRRD (itkNrrdImageIO.cxx): Adds explicit double and float template specializations of _dump_metadata_to_stream within the itk namespace.
  • PolygonGroupSpatialObject XML, FreeSurfer ASCII Mesh, VoxBoCUB: Each gets ConvertNumberToString on the relevant write path.
  • Tests: Four new precision round-trip sub-tests, one per module.

Minor issues noted: the joinElements helper in itkMetaImageIO.cxx is now unused after its one call site was replaced with a manual loop, and the VoxBoCUB precision test does not remove its temporary file.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/cleanup suggestions that do not affect correctness.

The core precision fix is correct and well-tested across all six IO modules. The only issues found are a now-unused static helper function (may generate a compiler warning) and a missing temp-file cleanup in the VoxBoCUB test — neither affects runtime behavior or test correctness. No P0 or P1 issues present.

Modules/IO/Meta/src/itkMetaImageIO.cxx — unused joinElements function may generate a compiler warning; Modules/Nonunit/Review/test/itkVoxBoCUBImageIOTest.cxx — precision test temp file not cleaned up.

Important Files Changed

Filename Overview
Modules/IO/NIFTI/src/itkNiftiImageIO.cxx All floating-point NIfTI metadata fields (pixdim, intent_p1/2/3, scl_slope/inter, cal_max/min, slice_duration, toffset, quaternion params, srow matrix) now use ConvertNumberToString; srow loop refactor is correct.
Modules/IO/Meta/src/itkMetaImageIO.cxx Fixes double/float scalar and vector metadata serialization; however, the joinElements static helper is now dead code after the vector path was replaced with a manual loop.
Modules/IO/Meta/include/itkMetaImageIO.h WriteMatrixInMetaData template now uses ConvertNumberToString for each matrix element; change is correct and complete.
Modules/IO/NRRD/src/itkNrrdImageIO.cxx Adds explicit double and float specializations of _dump_metadata_to_stream within the itk namespace; correct use of template specialization, no issues.
Modules/IO/MeshFreeSurfer/include/itkFreeSurferAsciiMeshIO.h Removes outputFile.precision(6) and std::fixed, replaces with ConvertNumberToString; correct fix for precision loss in point coordinate serialization.
Modules/Nonunit/Review/src/itkVoxBoCUBImageIO.cxx Spacing values now serialized with ConvertNumberToString; correct and minimal change.
Modules/IO/SpatialObjects/src/itkPolygonGroupSpatialObjectXMLFile.cxx Polygon vertex coordinates now serialized with ConvertNumberToString; correct fix.
Modules/Nonunit/Review/test/itkVoxBoCUBImageIOTest.cxx New precision sub-test verifies spacing round-trip, but the temporary precision file is never removed after the test completes.
Modules/IO/NIFTI/test/itkNiftiImageIOTest3.cxx New TestNiftiFloatMetadataPrecision function correctly verifies pixdim round-trip; proper cleanup on pass path.
Modules/IO/Meta/test/itkMetaImageIOMetaDataTest.cxx New double/float precision sub-tests verify bit-exact round-trip via stod/stof; well-structured and clear.
Modules/IO/NRRD/test/itkNrrdMetaDataTest.cxx Existing test logic inversion fixed (EXIT_SUCCESS/EXIT_FAILURE swapped back to correct sense); new precision sub-tests added correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[NIfTI / MetaImage / NRRD\nVoxBoCUB / FreeSurfer / PolygonXML\nIO Write Path] --> B{Numeric metadata\nfield}
    B -->|float / double| C["itk::ConvertNumberToString()\n(Google double-conversion ToShortest)\nShortest exact round-trip string"]
    B -->|int / string / other| D[Default ostream <<\nno change]
    C --> E[Text header / MetaDataDictionary string]
    D --> E
    E --> F[Disk file]
    F --> G[IO Read Path]
    G --> H["std::stof / std::stod\nor direct float parse"]
    H --> I{Bit-exact round-trip?}
    I -->|With fix: YES| J[Correct metadata value]
    I -->|Before fix: NO\n6-digit truncation| K[Different float bit-pattern]
Loading

Reviews (2): Last reviewed commit: "ENH: Add precision round-trip tests for ..." | Re-trigger Greptile

@hjmjohnson
Copy link
Copy Markdown
Member Author

Added a second commit that applies the same lossless float fix to MetaImage IO (itkMetaImageIO.cxx / itkMetaImageIO.h).

Three call sites corrected in the metadata serialization loop:

  • ExposeMetaData<double> and ExposeMetaData<float>ConvertNumberToString()
  • ExposeMetaData<std::vector<double>> → manual loop with ConvertNumberToString() (replacing joinElements which streamed with default precision)
  • WriteMatrixInMetaData<N>() template in the header → ConvertNumberToString() per element

All 18 MetaImage IO tests pass.

@hjmjohnson
Copy link
Copy Markdown
Member Author

Third commit: NRRD IO fix (itkNrrdImageIO.cxx).

The generic _dump_metadata_to_stream<T>() helper streamed scalar double and float metadata with default 6-digit precision. Added explicit template specializations for those two types that use itk::ConvertNumberToString().

Note: Array<double> and Array<float> were already lossless — their operator<< specializations in itkArrayOutputSpecialization.cxx already use ConvertNumberToString().

All 33 NRRD tests pass.

Copy link
Copy Markdown
Member

@dzenanz dzenanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good on a glance.

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 2, 2026
Adds test coverage to four IO modules verifying that float/double values
survive disk round-trips without the 6-digit truncation that default
stream precision introduces (ITK issue InsightSoftwareConsortium#3249):

* itkNiftiImageIOTest3.cxx: TestNiftiFloatMetadataPrecision() writes an
  image with 0.12345679-mm spacing, reads back, and verifies the
  MetaDataDictionary["pixdim[1]"] string parses to the exact float32.

* itkMetaImageIOMetaDataTest.cxx: encapsulates hpDouble=1.2345678901234568
  and hpFloat=1.2345679f, writes/reads MetaImage, checks exact equality
  via stod/stof (bypassing the loose 1e-6 tolerance in Equal<double>).

* itkNrrdMetaDataTest.cxx: same pattern for native double and float
  NRRD metadata keys.

* itkVoxBoCUBImageIOTest.cxx: creates an image with 0.12345678901234
  spacing, writes/reads CUB, asserts GetSpacing()[0] == original double.

These tests FAIL without the ConvertNumberToString fixes in PR InsightSoftwareConsortium#6003
and PASS after them (Closes InsightSoftwareConsortium#3249).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added the type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct label Apr 2, 2026
hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 2, 2026
Adds test coverage to four IO modules verifying that float/double values
survive disk round-trips without the 6-digit truncation that default
stream precision introduces (ITK issue InsightSoftwareConsortium#3249):

* itkNiftiImageIOTest3.cxx: TestNiftiFloatMetadataPrecision() writes an
  image with 0.12345679-mm spacing, reads back, and verifies the
  MetaDataDictionary["pixdim[1]"] string parses to the exact float32.

* itkMetaImageIOMetaDataTest.cxx: encapsulates hpDouble=1.2345678901234568
  and hpFloat=1.2345679f, writes/reads MetaImage, checks exact equality
  via stod/stof (bypassing the loose 1e-6 tolerance in Equal<double>).

* itkNrrdMetaDataTest.cxx: same pattern for native double and float
  NRRD metadata keys.

* itkVoxBoCUBImageIOTest.cxx: creates an image with 0.12345678901234
  spacing, writes/reads CUB, asserts GetSpacing()[0] == original double.

These tests FAIL without the ConvertNumberToString fixes in PR InsightSoftwareConsortium#6003
and PASS after them (Closes InsightSoftwareConsortium#3249).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hjmjohnson hjmjohnson force-pushed the fix-nifti-lossless-metadata branch from 0890665 to 203f858 Compare April 2, 2026 01:21
@hjmjohnson hjmjohnson marked this pull request as ready for review April 2, 2026 10:49
N-Dekker

This comment was marked as resolved.

@seanm

This comment was marked as resolved.

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 2, 2026
Remove the now-unused joinElements helper from itkMetaImageIO.cxx,
as its only call site was replaced with a ConvertNumberToString loop.

Add temp file cleanup in the VoxBoCUB precision sub-test.

Addresses review comments from @N-Dekker and @greptile-apps on PR InsightSoftwareConsortium#6003.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@N-Dekker N-Dekker dismissed their stale review April 2, 2026 17:18

My change request (removal of local joinElements function: #6003 (review)) is addressed, thanks Hans!

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 2, 2026
Adds test coverage to four IO modules verifying that float/double values
survive disk round-trips without the 6-digit truncation that default
stream precision introduces (ITK issue InsightSoftwareConsortium#3249):

* itkNiftiImageIOTest3.cxx: TestNiftiFloatMetadataPrecision() writes an
  image with 0.12345679-mm spacing, reads back, and verifies the
  MetaDataDictionary["pixdim[1]"] string parses to the exact float32.

* itkMetaImageIOMetaDataTest.cxx: encapsulates hpDouble=1.2345678901234568
  and hpFloat=1.2345679f, writes/reads MetaImage, checks exact equality
  via stod/stof (bypassing the loose 1e-6 tolerance in Equal<double>).

* itkNrrdMetaDataTest.cxx: same pattern for native double and float
  NRRD metadata keys.

* itkVoxBoCUBImageIOTest.cxx: creates an image with 0.12345678901234
  spacing, writes/reads CUB, asserts GetSpacing()[0] == original double.

These tests FAIL without the ConvertNumberToString fixes in PR InsightSoftwareConsortium#6003
and PASS after them (Closes InsightSoftwareConsortium#3249).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hjmjohnson hjmjohnson force-pushed the fix-nifti-lossless-metadata branch from 05e1541 to f7c9d52 Compare April 2, 2026 17:29
hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 2, 2026
Adds test coverage to four IO modules verifying that float/double values
survive disk round-trips without the 6-digit truncation that default
stream precision introduces (ITK issue InsightSoftwareConsortium#3249):

* itkNiftiImageIOTest3.cxx: TestNiftiFloatMetadataPrecision() writes an
  image with 0.12345679-mm spacing, reads back, and verifies the
  MetaDataDictionary["pixdim[1]"] string parses to the exact float32.

* itkMetaImageIOMetaDataTest.cxx: encapsulates hpDouble=1.2345678901234568
  and hpFloat=1.2345679f, writes/reads MetaImage, checks exact equality
  via stod/stof (bypassing the loose 1e-6 tolerance in Equal<double>).

* itkNrrdMetaDataTest.cxx: same pattern for native double and float
  NRRD metadata keys.

* itkVoxBoCUBImageIOTest.cxx: creates an image with 0.12345678901234
  spacing, writes/reads CUB, asserts GetSpacing()[0] == original double.

These tests FAIL without the ConvertNumberToString fixes in PR InsightSoftwareConsortium#6003
and PASS after them (Closes InsightSoftwareConsortium#3249).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hjmjohnson hjmjohnson force-pushed the fix-nifti-lossless-metadata branch from f7c9d52 to 4bc991c Compare April 2, 2026 17:32
@hjmjohnson hjmjohnson requested a review from N-Dekker April 2, 2026 20:27
@seanm

This comment was marked as resolved.

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 2, 2026
Adds test coverage to four IO modules verifying that float/double values
survive disk round-trips without the 6-digit truncation that default
stream precision introduces (ITK issue InsightSoftwareConsortium#3249):

* itkNiftiImageIOTest3.cxx: TestNiftiFloatMetadataPrecision() writes an
  image with 0.12345679-mm spacing, reads back, and verifies the
  MetaDataDictionary["pixdim[1]"] string parses to the exact float32.

* itkMetaImageIOMetaDataTest.cxx: encapsulates hpDouble=1.2345678901234568
  and hpFloat=1.2345679f, writes/reads MetaImage, checks exact equality
  via stod/stof (bypassing the loose 1e-6 tolerance in Equal<double>).

* itkNrrdMetaDataTest.cxx: same pattern for native double and float
  NRRD metadata keys.

* itkVoxBoCUBImageIOTest.cxx: creates an image with 0.12345678901234
  spacing, writes/reads CUB, asserts GetSpacing()[0] == original double.

These tests FAIL without the ConvertNumberToString fixes in PR InsightSoftwareConsortium#6003
and PASS after them (Closes InsightSoftwareConsortium#3249).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hjmjohnson hjmjohnson force-pushed the fix-nifti-lossless-metadata branch from 4bc991c to e90b1bb Compare April 2, 2026 22:21
@hjmjohnson

This comment was marked as resolved.

@seanm
Copy link
Copy Markdown
Contributor

seanm commented Apr 3, 2026

@seanm I plan to merge this at the end of the workday today (2026-04-03) if no further change requests or delay requests are made.

Please delay. Today is a holiday here, but I did determine at the end of yesterday that this breaks several of our tests. Likely just slight numerical differences, but I'd like to check early next week.

Copy link
Copy Markdown
Contributor

@N-Dekker N-Dekker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Hans! I think it's OK now, but you might of course consider my "nitpicks" 😺

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 3, 2026
WritePoints() is defined inside namespace itk, so the itk:: prefix on
ConvertNumberToString is redundant.

Suggested by N-Dekker in PR InsightSoftwareConsortium#6003.
@N-Dekker

This comment was marked as resolved.

seanm
seanm previously requested changes Apr 3, 2026
Copy link
Copy Markdown
Contributor

@seanm seanm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please delay. Today is a holiday here, but I did determine at the end of yesterday that this breaks several of our tests. Likely just slight numerical differences, but I'd like to check early next week.

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 3, 2026
AllocateInitialized() is an alias for Allocate(true) introduced in
itkImageBase.h to make zero-initialization intent explicit in code.
Replace all call sites in Modules/ and Examples/.

The trailing "// initialize buffer to zero" comments are also removed
since AllocateInitialized() is self-documenting.

Suggested by N-Dekker in ITK PR InsightSoftwareConsortium#6003.
hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Apr 3, 2026
Replace the verbose two-line FixedArray initialization pattern:

  SomeType::SizeType size;
  size.Fill(N);

with the modern single-line form using the static factory method:

  constexpr auto size = SomeType::SizeType::Filled(N);   // test files
  auto var = VectorType::Filled(0.0);                    // template .hxx

::Filled(v) is a static method on itk::FixedArray (and all subclasses
including SizeType, IndexType, SpacingType, PointType, VectorType, etc.)
that returns an instance with all elements set to v.

constexpr is used where the variable is never subsequently modified.
auto (without constexpr) is used for accumulator variables in loop bodies.

Skipped: cases where Fill() is followed by element-wise overrides
(size[0] = w; size[1] = h;) or where typename disambiguation in nested
template scopes makes the replacement non-trivial.

Suggested by N-Dekker in ITK PR InsightSoftwareConsortium#6003.
@hjmjohnson
Copy link
Copy Markdown
Member Author

@seanm Understood — I'll hold off on merging. Please take the time you need to investigate; early next week is fine.

When you have a chance, it would help to know:

  • Which test(s) fail and in what format/module (NIfTI, MetaImage, NRRD, …)?
  • Are the failures hard errors (wrong read-back values) or tolerance checks that need adjusting for the higher-precision strings?

The change from 6 significant digits (default ostream) to shortest-round-trip precision is intentional, so any downstream comparisons against hardcoded 6-digit strings will need updating — happy to help with that once you share specifics.

@github-actions github-actions bot added the area:Documentation Issues affecting the Documentation module label Apr 3, 2026
@N-Dekker
Copy link
Copy Markdown
Contributor

N-Dekker commented Apr 7, 2026

@hjmjohnson Do I understand correctly that some output files may become much larger, with this PR? Especially when writing data "for all points" (for example, their coordinates)...?

hjmjohnson and others added 7 commits April 8, 2026 00:14
SetImageIOMetadataFromNIfTI() was using std::ostringstream to convert
float fields from the nifti_image struct to strings, which applies the
default 6-digit stream precision and silently truncates values.

Replace all float-field conversions with itk::ConvertNumberToString(),
which uses Google's double-conversion ToShortest() algorithm (via
ITKDoubleConversion) to produce the shortest decimal string that
round-trips exactly through strtof. Integer and char* fields are
unchanged.

Fields corrected: intent_p1/p2/p3, pixdim[0-7], scl_slope, scl_inter,
cal_max, cal_min, slice_duration, toffset, quatern_b/c/d,
qoffset_x/y/z, srow_x/y/z.

Fixes: InsightSoftwareConsortium#3249
Non-template MetaData fields of type double, float, vector<double>, and
Matrix<double> were serialized via std::ostringstream with default 6-digit
precision, silently truncating values with more than 6 significant digits.

Replace the lossy stream insertions with itk::ConvertNumberToString(),
which wraps Google double-conversion::ToShortest() and produces the
shortest decimal string that round-trips exactly through strtod/strtof.
Three call sites are affected:
- ExposeMetaData<double> / ExposeMetaData<float> in WriteImageInformation()
- ExposeMetaData<std::vector<double>> (manual loop replacing joinElements)
- WriteMatrixInMetaData<N>() template in the header

See: InsightSoftwareConsortium#3249

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_dump_metadata_to_stream() used default stream precision (6 significant
digits) for float and double values, silently truncating user-defined
metadata stored in NRRD key/value fields.  Use if constexpr to dispatch
float and double types to itk::ConvertNumberToString() for
shortest-round-trip serialization, while leaving all other types on the
default stream path.  Array<double> and Array<float> were already
handled losslessly via the specialized operator<< in
itkArrayOutputSpecialization.cxx.

See: InsightSoftwareConsortium#3249

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two persistence-layer paths used lossy default stream precision for
floating-point coordinates:

itkPolygonGroupSpatialObjectXMLFile.cxx: PolygonSpatialObject vertex
coordinates (Point<double,3>) were streamed to the XML file with default
6-digit precision.  Replace with itk::ConvertNumberToString() per component.

itkFreeSurferAsciiMeshIO.h WritePoints<T>: hardcoded precision(6) with
std::fixed limited output to 6 decimal places regardless of T.  Replace
with itk::ConvertNumberToString() for both float and double specializations,
producing the shortest decimal string that round-trips exactly.

See: InsightSoftwareConsortium#3249

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
m_Spacing[] values (double) were written to the CUB text header with
default stream precision (6 significant digits), silently truncating
sub-millimeter or high-precision spacing values.  Replace with
itk::ConvertNumberToString() for shortest-round-trip serialization.

See: InsightSoftwareConsortium#3249

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds test coverage to four IO modules verifying that float/double values
survive disk round-trips without the 6-digit truncation that default
stream precision introduces (ITK issue InsightSoftwareConsortium#3249):

* itkNiftiImageIOTest3.cxx: TestNiftiFloatMetadataPrecision() writes an
  image with 0.12345679-mm spacing, reads back, and verifies the
  MetaDataDictionary["pixdim[1]"] string parses to the exact float32.

* itkMetaImageIOMetaDataTest.cxx: encapsulates hpDouble=1.2345678901234568
  and hpFloat=1.2345679f, writes/reads MetaImage, checks exact equality
  via stod/stof (bypassing the loose 1e-6 tolerance in Equal<double>).

* itkNrrdMetaDataTest.cxx: same pattern for native double and float
  NRRD metadata keys.

* itkVoxBoCUBImageIOTest.cxx: creates an image with 0.12345678901234
  spacing, writes/reads CUB, asserts GetSpacing()[0] == original double.

These tests FAIL without the ConvertNumberToString fixes in PR InsightSoftwareConsortium#6003
and PASS after them (Closes InsightSoftwareConsortium#3249).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uide

Several IO modules (NIfTI, MetaImage, NRRD, VoxBoCUB, SpatialObject, Mesh)
now use itk::ConvertNumberToString() for float/double metadata, replacing
the 6-significant-digit default. Tests comparing raw header text against
golden baselines may need updating.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hjmjohnson hjmjohnson force-pushed the fix-nifti-lossless-metadata branch from 998aaec to cd9ec88 Compare April 8, 2026 00:23
@hjmjohnson
Copy link
Copy Markdown
Member Author

@seanm Do you need any assistance with this task?

@seanm
Copy link
Copy Markdown
Contributor

seanm commented Apr 8, 2026

When you have a chance, it would help to know:

* Which test(s) fail and in what format/module (NIfTI, MetaImage, NRRD, …)?

nifti. I don't use the others.

* Are the failures hard errors (wrong read-back values) or tolerance checks that need adjusting for the higher-precision strings?

tolerance checks. I just need to confirm the results are not wildly different... should know today...

@seanm seanm self-requested a review April 9, 2026 01:42
@seanm seanm dismissed their stale review April 9, 2026 01:42

OK all is well, the differences are small.

@hjmjohnson hjmjohnson merged commit 1e73a2b into InsightSoftwareConsortium:main Apr 9, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Documentation Issues affecting the Documentation module area:IO Issues affecting the IO module type:Enhancement Improvement of existing methods or implementation 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.

String conversions to EncapsulateMetaData should attempt to be lossless.

4 participants