-
Notifications
You must be signed in to change notification settings - Fork 462
Description
Describe the Bug
When loading NIfTI volumes whose main slice plane is coronal or sagittal, the computed imagePositionPatient values are incorrect, resulting in wrong z-spacing from calculateSpacingBetweenImageIds and a distorted (squished/stretched) volume.
The bug is in
cornerstone3D/packages/nifti-volume-loader/src/createNiftiImageIdsAndCacheMetadata.ts
Line 295 in 4ea8515
| const imagePositionPatient = [ |
const imagePositionPatient = [
parseFloat((origin[0] + imageIdIndex * direction[6] * spacing[0]).toFixed(precision)),
parseFloat((origin[1] + imageIdIndex * direction[7] * spacing[1]).toFixed(precision)),
parseFloat((origin[2] + imageIdIndex * direction[8] * spacing[2]).toFixed(precision)),
];
Since direction[6..8] is the scan-axis-normal (k-axis direction cosine), all three components should use spacing[2] (the slice/k-axis spacing). position_k = origin + k × spacing[2] × scanAxisNormal
Steps to Reproduce
- Load any NIfTI file acquired in coronal orientation (or with a non-identity affine that maps the k-axis to a non-Z direction in LPS)
- The volume will have in-plane pixel spacing ≠ slice spacing
- Use createNiftiImageIdsAndCacheMetadata → volumeLoader.createAndCacheVolume
- Observe that the volume appears squished/stretched along the slice direction
The current behavior
When loading a non-axial NIfTI volume (e.g., coronal with in-plane spacing 0.5mm and slice spacing 2.0mm):
- createNiftiImageIdsAndCacheMetadata computes imagePositionPatient per slice using the wrong spacing for each world-axis component. For a coronal volume (scanAxisNormal ≈ [0, 1, 0]), the y-component uses spacing[1] (0.5mm, the row pixel spacing) instead of spacing[2] (2.0mm, the slice spacing). The cached slice positions are therefore spaced 0.5mm apart instead of 2.0mm.
- When volumeLoader.createAndCacheVolume is called, the chain generateVolumePropsFromImageIds → sortImageIdsAndGetSpacing → calculateSpacingBetweenImageIds reads back these incorrect positions and computes zSpacing = 0.5mm by measuring the dot-product distance between them along the scan axis.
- The volume is constructed with spacing = [pixelSpacing[1], pixelSpacing[0], zSpacing] = [0.5, 0.5, 0.5] instead of the correct [0.5, 0.5, 2.0].
- Visual result: the rendered volume is squished by a factor of 4 along the slice stacking direction (anterior-posterior for coronal). Anatomy that should span ~180mm appears ~45mm deep. MPR cross-sections through the volume show severely distorted proportions — circles appear as ellipses, organs look flattened.
- For sagittal volumes the same class of error occurs on the x-component. For oblique orientations, multiple components are wrong simultaneously, causing both scaling distortion and shear.
- Axial volumes are unaffected because the scan axis normal is [0, 0, 1] — the zero components in x and y mask the wrong spacing values, and the z-component happens to use spacing[2] correctly by coincidence.
The expected behavior
The volume should render with correct proportions. The inter-slice spacing used by calculateSpacingBetweenImageIds should equal spacing[2] from the NIfTI affine, not spacing[0] or spacing[1].
System Information
System:
OS: macOS 15.7.3
CPU: (14) arm64 Apple M4 Pro
Memory: 694.00 MB / 48.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.19.5 - /Users/zlin/.nvm/versions/node/v20.19.5/bin/node
npm: 10.9.2 - /Users/zlin/.nvm/versions/node/v20.19.5/bin/npm
Browsers:
Chrome: 144.0.7559.133
Firefox: 147.0.2
Safari: 18.6