Skip to content

Conversation

@slicepaste
Copy link
Contributor

@slicepaste slicepaste commented Dec 23, 2024

Fixes #6840

Description

According to the issue, this PR addresses on the meta dictionary data['pixdim'] of NIfTI images does not update after applying the spacing or spacingd. To align with affine, we update data['pixdim'] and keep the original metainfo in data['original_pixdim']. Additionally, this PR also update the metainfo key_{meta_key_postfix}['pixdim'] in NIfTI images, consistent with spaced_data_dict['image_meta_dict']['pixdim'] in issue #6832.

Types of changes

  • Non-breaking change (fix or new feature that would not break existing functionality).
  • Breaking change (fix or new feature that would cause existing functionality to change).
  • New tests added to cover the changes.
  • Integration tests passed locally by running ./runtests.sh -f -u --net --coverage.
  • Quick tests passed locally by running ./runtests.sh --quick --unittests --disttests.
  • In-line docstrings updated.
  • Documentation updated, tested make html command in the docs/ folder.

According to the issue, this PR addresses on the meta dictionary `data['pixdim']` of NIfTI images does not update after applying the `spacing` or `spacingd`.
To align with `affine`, we update `data['pixdim']` and keep the original metainfo in `data['original_pixdim']`.
Additionally, this PR also update the metainfo `key_{meta_key_postfix}['pixdim']` in NIfTI images, consistent with `spaced_data_dict['image_meta_dict']['pixdim']` in issue Project-MONAI#6832.

Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing <[email protected]>
@slicepaste slicepaste changed the title Fixes #6840 Update ['pixdim'] after Spacing transform in meta dict. Dec 23, 2024
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Copy link
Contributor

@KumoLiu KumoLiu left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution! Leave several comments inline.

@atbenmurray
Copy link
Contributor

atbenmurray commented Jan 17, 2025

Hi folks. I had a look through the code and the PR, but I need to do a deeper evaluation of how this intersects with lazy resampling and the consistency of behaviour between MetaTensor and _meta_dict modes

@KumoLiu KumoLiu requested a review from ericspod January 17, 2025 15:28
@atbenmurray
Copy link
Contributor

In the middle of extending test scenarios. Please bear with me.

@slicepaste
Copy link
Contributor Author

@einsyang723 @IamTingTing The current PR is not our final version, and it has remained unchanged for a while.
We should continue working on this issue and update the final version ASAP so we can move forward with this PR.
Let me know if you need any help. Thanks!

slicepaste and others added 11 commits February 17, 2025 16:22
Previously, a method was defined in `meta_tensor`, and using `data_array.clone()` introduced additional costs.
After reevaluating the code, modifying the `TraceableTransform.track_transform_meta()` method executed by `SpatialResample` makes it much cleaner and more maintainable.

Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Previously, a method was defined in `meta_tensor`, and using `data_array.clone()` introduced additional costs.
After reevaluating the code, modifying the `TraceableTransform.track_transform_meta()` method executed by `SpatialResample` makes it much cleaner and more maintainable.

Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
@atbenmurray
Copy link
Contributor

Picking this up again. Will have a review for you all today.

slicepaste and others added 15 commits March 6, 2025 17:25
According to the issue, this PR addresses on the meta dictionary `data['pixdim']` of NIfTI images does not update after applying the `spacing` or `spacingd`.
To align with `affine`, we update `data['pixdim']` and keep the original metainfo in `data['original_pixdim']`.
Additionally, this PR also update the metainfo `key_{meta_key_postfix}['pixdim']` in NIfTI images, consistent with `spaced_data_dict['image_meta_dict']['pixdim']` in issue Project-MONAI#6832.

Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Previously, a method was defined in `meta_tensor`, and using `data_array.clone()` introduced additional costs.
After reevaluating the code, modifying the `TraceableTransform.track_transform_meta()` method executed by `SpatialResample` makes it much cleaner and more maintainable.

Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Checking all dictionary keys that start with `{key}_` to support custom settings of `meta_keys` and `meta_key_postfix`.
This ensures that no matter how users configure the naming conventions in `LoadImaged`,
we can correctly synchronize metadata from the MetaTensor to the corresponding meta dictionary.

Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
Signed-off-by: Wei_Chuan, Chiang <[email protected]>
Co-authored-by: einsyang723 <[email protected]>
Co-authored-by: IamTingTing  <[email protected]>
@atbenmurray
Copy link
Contributor

@einsyang723 @slicepaste @ericspod @KumoLiu @IamTingTing
Hi folks. Sorry it has been a while but I have a candidate PR #8411 that I think is important to address as a prerequisite to this PR. It is still in draft phase but take a look when you have a moment

Copy link
Contributor

@atbenmurray atbenmurray left a comment

Choose a reason for hiding this comment

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

The change made in monai/transforms/spatial/dictionary.py means the issue that I raised is much less relevant now

@slicepaste
Copy link
Contributor Author

The change made in monai/transforms/spatial/dictionary.py means the issue that I raised is much less relevant now

@atbenmurray Thank you for the approval. Just to confirm our understanding — this PR can be merged first, and the remaining concerns regarding meta_keys, meta_key_postfix, and the additional metadata dictionary changes will be addressed in PR #8411 , right?

@atbenmurray
Copy link
Contributor

The change made in monai/transforms/spatial/dictionary.py means the issue that I raised is much less relevant now

@atbenmurray Thank you for the approval. Just to confirm our understanding — this PR can be merged first, and the remaining concerns regarding meta_keys, meta_key_postfix, and the additional metadata dictionary changes will be addressed in PR #8411 , right?

Exactly. Yes. This PR is decoupled from PR #8411 now.

@slicepaste
Copy link
Contributor Author

Exactly. Yes. This PR is decoupled from PR #8411 now.

Thanks for the confirmation. Looking forward to the merge!

Copy link
Member

@ericspod ericspod left a comment

Choose a reason for hiding this comment

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

I think everything has been addressed here, @KumoLiu please trigger blossom and we'll integrate this soon. Thanks!

@yakovdan
Copy link

Hi, is there any progress on merging this? Perhaps blossom-ci is stuck?

@ericspod ericspod requested a review from Nic-Ma as a code owner October 30, 2025 13:04
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 30, 2025

Walkthrough

The changes add support for tracking original pixel dimension metadata through image transformations. New enum members PIXDIM and ORIGINAL_PIXDIM are introduced to MetaKeys. The image reader now preserves the original pixdim as a defensive copy. The inverse transform synchronizes pixdim with spacing computed from updated affine values. The dictionary spacing transform conditionally propagates metadata for NIfTI-format inputs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify the defensive copy of PIXDIM in image_reader.py is created correctly when the metadata key exists
  • Confirm the affine_to_spacing calculation in inverse.py properly updates PIXDIM values at indices 1+
  • Check that metadata propagation in dictionary.py only applies to supported NIfTI formats as intended
  • Ensure enum additions (PIXDIM, ORIGINAL_PIXDIM) are correctly referenced across all modified files
  • Validate that the conditional propagation logic correctly identifies related metadata keys

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description includes the required template sections with the issue reference (#6840), a clear description of the problem and solution (updating pixdim and preserving original value in original_pixdim), and mentions handling both data['pixdim'] and key_{meta_key_postfix}['pixdim']. However, all checkbox items in the "Types of changes" section remain unchecked, which is inconsistent with the nature of the changes presented (they should minimally indicate this is a non-breaking change and that relevant testing/documentation updates have been performed).
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Update ['pixdim'] after Spacing transform in meta dict." is specific and directly captures the primary change in the PR. It clearly indicates that the PR updates the pixdim metadata after applying spacing transforms in a dictionary context, which aligns with the main objectives of fixing issue #6840 and ensuring pixdim is updated consistently with affine changes.
Linked Issues Check ✅ Passed The PR implementation aligns with issue #6840's objectives: new PIXDIM and ORIGINAL_PIXDIM enum members are added to MetaKeys; image_reader.py now creates a defensive copy of PixDim as ORIGINAL_PIXDIM when present; inverse.py updates PIXDIM based on new affine after spacing transforms; and spatial/dictionary.py propagates metadata in the spacingd dictionary transform. The changes preserve the original pixdim value while updating it post-transform, mirroring the affine handling pattern referenced in the issue.
Out of Scope Changes Check ✅ Passed All modifications in the four affected files directly serve the issue #6840 objectives: the enum additions define the new metadata keys, image_reader.py and inverse.py preserve and update pixdim correctly, and spatial/dictionary.py ensures metadata consistency in dictionary transforms. No unrelated changes or scope creep is evident; each alteration contributes to the unified goal of managing pixdim metadata post-spacing-transform.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

Updates NIfTI metadata handling to ensure pixdim field is correctly updated after Spacing transforms, addressing issues #6840 and #6832 where spacing metadata remained stale.

Key changes:

  • Added MetaKeys.PIXDIM and MetaKeys.ORIGINAL_PIXDIM enum constants for consistent metadata key naming
  • Modified NibabelReader to preserve original pixdim values in original_pixdim before any transforms (parallels existing original_affine pattern)
  • Updated TraceableTransform to recalculate and update pixdim[1:] based on the new affine matrix after spatial transforms, preserving pixdim[0] (qfac orientation flag)
  • Enhanced Spacingd dictionary transform to propagate updated metadata from transformed MetaTensors to their associated meta dict keys (e.g., image_meta_dict) for NIfTI files

Potential concerns:

  • No new unit tests were added to verify the pixdim update behavior
  • The code in inverse.py:229 assumes pixdim is a mutable array-like object (slice assignment won't work on tuples)
  • The Spacingd meta dict update only occurs for NIfTI files, relying on filename extension checking

Confidence Score: 4/5

  • Safe to merge with minor testing concerns - functionality appears correct but lacks comprehensive test coverage
  • The implementation correctly addresses the reported issues and follows existing patterns (e.g., original_affine). The logic preserves the NIfTI qfac value and properly updates spatial dimensions. However, the absence of new tests and reliance on pixdim mutability (which should work for numpy arrays from nibabel headers) introduces some uncertainty about edge cases.
  • monai/transforms/inverse.py:229 - verify pixdim is always mutable; monai/transforms/spatial/dictionary.py:531 - confirm meta_key dict type assumptions hold

Important Files Changed

File Analysis

Filename Score Overview
monai/utils/enums.py 5/5 Added PIXDIM and ORIGINAL_PIXDIM enum values to MetaKeys for metadata tracking
monai/data/image_reader.py 5/5 Added logic to preserve original pixdim values by storing a copy in ORIGINAL_PIXDIM when loading NIfTI images
monai/transforms/inverse.py 4/5 Updates pixdim[1:] to match the new spacing derived from the updated affine matrix, preserving pixdim[0] (qfac)
monai/transforms/spatial/dictionary.py 4/5 Added logic to propagate updated metadata (including pixdim) from transformed data to associated meta dict keys for NIfTI files

Sequence Diagram

sequenceDiagram
    participant User
    participant LoadImaged
    participant NibabelReader
    participant Spacingd
    participant TraceableTransform
    participant MetaDict

    User->>LoadImaged: Load NIfTI image
    LoadImaged->>NibabelReader: read(filename)
    NibabelReader->>NibabelReader: _get_meta_dict(img)
    Note over NibabelReader: Extract header as dict<br/>including pixdim field
    NibabelReader->>NibabelReader: Save original_pixdim = copy(pixdim)
    NibabelReader-->>LoadImaged: image data + metadata
    LoadImaged->>MetaDict: Store image_meta_dict
    Note over MetaDict: Contains pixdim and<br/>original_pixdim
    LoadImaged-->>User: data dict with MetaTensor

    User->>Spacingd: Apply spacing transform
    Spacingd->>TraceableTransform: Process with new spacing
    TraceableTransform->>TraceableTransform: Update affine matrix
    TraceableTransform->>TraceableTransform: Calculate spacing from affine
    TraceableTransform->>TraceableTransform: Update pixdim[1:4] with new spacing
    Note over TraceableTransform: Preserves pixdim[0] (qfac)<br/>Updates only spatial dims
    TraceableTransform-->>Spacingd: Transformed MetaTensor
    
    Spacingd->>Spacingd: Check if NIfTI file
    alt Is NIfTI format
        Spacingd->>MetaDict: Update image_meta_dict with new meta
        Note over MetaDict: Propagates updated pixdim<br/>to meta dict keys
    end
    
    Spacingd-->>User: Transformed data with updated pixdim
Loading

4 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
monai/transforms/spatial/dictionary.py (1)

525-532: Guard meta propagation to dict-typed keys only.

Prevents accidental .update() on non-dicts (e.g., future keys). Functionality unchanged.

Apply:

-            if isinstance(d[key], MetaTensor):
-                meta_keys = [k for k in d.keys() if k is not None and k.startswith(f"{key}_")]
-                for meta_key in meta_keys:
-                    if "filename_or_obj" in d[key].meta and is_supported_format(
-                        d[key].meta["filename_or_obj"], ["nii", "nii.gz"]
-                    ):
-                        d[meta_key].update(d[key].meta)
+            if isinstance(d[key], MetaTensor):
+                fn = d[key].meta.get("filename_or_obj")
+                if fn is not None and is_supported_format(fn, ["nii", "nii.gz"]):
+                    for mk, mv in d.items():
+                        if mk and mk.startswith(f"{key}_") and isinstance(mv, dict):
+                            mv.update(d[key].meta)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 69f3dd2 and 13feb1d.

📒 Files selected for processing (4)
  • monai/data/image_reader.py (1 hunks)
  • monai/transforms/inverse.py (2 hunks)
  • monai/transforms/spatial/dictionary.py (2 hunks)
  • monai/utils/enums.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

⚙️ CodeRabbit configuration file

Review the Python code for quality and correctness. Ensure variable names adhere to PEP8 style guides, are sensible and informative in regards to their function, though permitting simple names for loop and comprehension variables. Ensure routine names are meaningful in regards to their function and use verbs, adjectives, and nouns in a semantically appropriate way. Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings. Examine code for logical error or inconsistencies, and suggest what may be changed to addressed these. Suggest any enhancements for code improving efficiency, maintainability, comprehensibility, and correctness. Ensure new or modified definitions will be covered by existing or new unit tests.

Files:

  • monai/transforms/inverse.py
  • monai/utils/enums.py
  • monai/data/image_reader.py
  • monai/transforms/spatial/dictionary.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: min-dep-py3 (3.12)
  • GitHub Check: min-dep-os (ubuntu-latest)
  • GitHub Check: min-dep-pytorch (2.5.1)
  • GitHub Check: min-dep-py3 (3.11)
  • GitHub Check: min-dep-pytorch (2.8.0)
  • GitHub Check: min-dep-py3 (3.10)
  • GitHub Check: min-dep-os (windows-latest)
  • GitHub Check: min-dep-py3 (3.9)
  • GitHub Check: min-dep-os (macOS-latest)
  • GitHub Check: min-dep-pytorch (2.6.0)
  • GitHub Check: min-dep-pytorch (2.7.1)
  • GitHub Check: flake8-py3 (mypy)
  • GitHub Check: flake8-py3 (pytype)
  • GitHub Check: quick-py3 (ubuntu-latest)
  • GitHub Check: packaging
  • GitHub Check: quick-py3 (windows-latest)
  • GitHub Check: build-docs
  • GitHub Check: quick-py3 (macOS-latest)
  • GitHub Check: flake8-py3 (codeformat)
🔇 Additional comments (5)
monai/data/image_reader.py (1)

1115-1116: Defensive copy of pixdim looks good. Please add a test.

Solid, minimal change. Add/extend a NIfTI load test to assert ORIGINAL_PIXDIM is a copy and untouched by later mutations.

monai/utils/enums.py (1)

545-547: MetaKeys additions LGTM.

Additive, consistent with downstream usage.

monai/transforms/inverse.py (2)

25-25: Import addition is appropriate.


227-230: No changes needed; review comment is incorrect.

The affine_to_spacing function is designed to accept both numpy and torch tensors (via NdarrayTensor union type). The implementation explicitly handles torch.Tensor at line 727-728 with a dedicated compute path. Adding .numpy() coercion would contradict the function's design and unnecessarily force type conversion.

Likely an incorrect or invalid review comment.

monai/transforms/spatial/dictionary.py (1)

32-32: Import addition is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modify pixdim to original_pixdim in meta dict

6 participants