Skip to content

Conversation

@karllandheer
Copy link

Adds RandNonCentralChiNoise and RandNonCentralChiNoised, which generalize Rician noise to k degrees of freedom. Standard brain MRI typically uses 32 (or more) quadrature coils, so accurate noise simulation requires this modification, especially in the low SNR limit.

Includes array, dictionary, and test files.

Fixes # .

Description

A few sentences describing the changes proposed in this pull request.

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.

Adds RandNonCentralChiNoise and RandNonCentralChiNoised,
which generalize Rician noise to k degrees of freedom. Standard brain MRI typically uses 32 (or more) quadrature coils, so accurate noise simulation requires this modification, especially in the low SNR limit

Includes array, dictionary, and test files.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 2, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR adds a new transform RandNonCentralChiNoise (array variant) and a dictionary wrapper RandNonCentralChiNoised, exports them via monai/transforms/init.py, updates CHANGELOG.md to list the new public entities, adds .gitignore entry monai-dev/, and includes unit tests for both the array and dictionary transforms (tests/transforms/test_rand_noncentralchi_noise.py and tests/transforms/test_rand_noncentralchi_noised.py). The array transform implements channel-wise and global modes, DoF validation, relative scaling and dtype handling; the dictionary class wraps and propagates RNG state and provides D/Dict aliases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Key areas to inspect:

  • monai/transforms/intensity/array.py: correctness of the non-central chi noise generation and root-sum-square aggregation
  • monai/transforms/intensity/dictionary.py: RNG propagation, MapTransform behavior, and aliasing
  • tests/transforms/*: determinism of seeded tests and that expected-value reconstruction matches implementation
  • init.py and CHANGELOG.md: public exports and changelog accuracy

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: Add RandNonCentralChiNoise transform" directly and clearly summarizes the primary change in the PR. The changeset introduces the RandNonCentralChiNoise transform along with related dictionary variants and tests, which matches the title's focus on adding this new transform. The language is concise, specific, and uses standard commit conventions, making it immediately clear to anyone reviewing history what was added.
Description Check ✅ Passed The PR description includes all required template sections: an introductory explanation of the change and its motivation, a "Fixes #" line, and a completed "Types of changes" section with multiple relevant items checked (non-breaking change, new tests added, quick tests passed, docstrings updated). While the template's "Description" section contains placeholder text rather than being replaced inline, the actual description content is provided comprehensively at the start of the PR, explaining what transforms were added and why they're needed for accurate brain MRI noise simulation.
✨ 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

This PR adds RandNonCentralChiNoise and RandNonCentralChiNoised transforms to generalize Rician noise simulation for modern MRI systems with multiple quadrature coils.

Key Changes:

  • Implemented RandNonCentralChiNoise in array.py with configurable degrees of freedom (k), defaulting to 64 for typical 32-channel head coils
  • Added dictionary-based wrapper RandNonCentralChiNoised following MONAI patterns
  • Comprehensive test coverage for both k=64 and k=2 (Rician) cases with numerical validation
  • Updated exports in __init__.py and CHANGELOG
  • Mathematical implementation: computes sqrt(sum(k Gaussian noise arrays²)) where first array includes the signal

Implementation Quality:

  • Follows existing RandRicianNoise patterns closely for consistency
  • Supports both NumPy and PyTorch backends
  • Proper validation for degrees_of_freedom parameter (must be int ≥ 1)
  • Channel-wise and relative noise options maintained
  • Tests verify correctness against manual calculations

Confidence Score: 5/5

  • Safe to merge - well-tested feature addition with proper validation and comprehensive test coverage
  • Clean implementation following established patterns, comprehensive tests covering edge cases (k=2 Rician case and k=64), proper input validation, and no breaking changes to existing code
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
monai/transforms/intensity/array.py 4/5 Added RandNonCentralChiNoise transform generalizing Rician noise to k degrees of freedom with proper validation and both NumPy/Torch support
monai/transforms/intensity/dictionary.py 5/5 Added RandNonCentralChiNoised dictionary wrapper with proper random state management and key iteration
tests/transforms/test_rand_noncentralchi_noise.py 5/5 Comprehensive tests for array transform covering zero/non-zero mean, k=64 and k=2 (Rician) cases with numerical validation
tests/transforms/test_rand_noncentralchi_noised.py 5/5 Comprehensive tests for dictionary transform covering multiple keys, k=64 and k=2 cases with numerical validation

Sequence Diagram

sequenceDiagram
    participant User
    participant RandNonCentralChiNoise
    participant RandomizableTransform
    participant _add_noise
    participant NumPy/Torch

    User->>RandNonCentralChiNoise: __call__(img, randomize=True)
    RandNonCentralChiNoise->>RandNonCentralChiNoise: convert_to_tensor(img, dtype)
    
    RandNonCentralChiNoise->>RandomizableTransform: randomize(None)
    RandomizableTransform-->>RandNonCentralChiNoise: set _do_transform based on prob
    
    alt not _do_transform
        RandNonCentralChiNoise-->>User: return original img
    else _do_transform
        alt channel_wise
            loop for each channel
                RandNonCentralChiNoise->>_add_noise: _add_noise(channel, mean, std, k)
                _add_noise->>NumPy/Torch: sample k Gaussian noise arrays
                _add_noise->>_add_noise: add img to first noise array
                _add_noise->>NumPy/Torch: compute sqrt(sum(noises²))
                _add_noise-->>RandNonCentralChiNoise: noised channel
            end
        else not channel_wise
            RandNonCentralChiNoise->>_add_noise: _add_noise(img, mean, std, k)
            _add_noise->>NumPy/Torch: sample k Gaussian noise arrays (shape: k×img.shape)
            _add_noise->>_add_noise: add img to first noise array [0]
            _add_noise->>NumPy/Torch: compute sqrt(sum(noises²)) along axis 0
            _add_noise-->>RandNonCentralChiNoise: noised img
        end
        RandNonCentralChiNoise-->>User: return noised img
    end
Loading

5 files reviewed, 2 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
monai/transforms/intensity/dictionary.py (1)

70-174: Missing public exports for alias variants.

The aliases RandNonCentralChiNoiseD and RandNonCentralChiNoiseDict (defined at line 2032) must be added to __all__ to match the pattern of other transforms.

Add these entries after line 73:

     "RandGaussianNoised",
     "RandRicianNoised",
     "RandNonCentralChiNoised",
+    "RandNonCentralChiNoiseD",
+    "RandNonCentralChiNoiseDict",
     "ShiftIntensityd",
🧹 Nitpick comments (2)
monai/transforms/intensity/array.py (1)

176-176: Missing space after comment hash.

Line 176 comment should be # 64 default because... not #64 default because...

Apply this diff:

-        degrees_of_freedom: int = 64, #64 default because typical modern brain MRI is 32 quadrature coils
+        degrees_of_freedom: int = 64,  # 64 default because typical modern brain MRI is 32 quadrature coils
tests/transforms/test_rand_noncentralchi_noise.py (1)

33-33: Missing space after comment hash.

Line 33 should be # 64 is common... not #64 is common...

Apply this diff:

-        degrees_of_freedom = 64  #64 is common due to 32 channel head coil
+        degrees_of_freedom = 64  # 64 is common due to 32 channel head coil
📜 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 69444c1 and c6b4946.

📒 Files selected for processing (7)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • monai/transforms/__init__.py (2 hunks)
  • monai/transforms/intensity/array.py (2 hunks)
  • monai/transforms/intensity/dictionary.py (4 hunks)
  • tests/transforms/test_rand_noncentralchi_noise.py (1 hunks)
  • tests/transforms/test_rand_noncentralchi_noised.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.md

⚙️ CodeRabbit configuration file

Remember that documentation must be updated with the latest information.

Files:

  • CHANGELOG.md
**/*.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:

  • tests/transforms/test_rand_noncentralchi_noise.py
  • monai/transforms/intensity/array.py
  • monai/transforms/intensity/dictionary.py
  • tests/transforms/test_rand_noncentralchi_noised.py
  • monai/transforms/__init__.py
🪛 Ruff (0.14.2)
monai/transforms/intensity/array.py

169-169: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


187-187: Avoid specifying long messages outside the exception class

(TRY003)


237-237: Avoid specifying long messages outside the exception class

(TRY003)


239-239: Avoid specifying long messages outside the exception class

(TRY003)


242-242: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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: packaging
  • GitHub Check: build-docs
  • GitHub Check: flake8-py3 (pytype)
  • GitHub Check: quick-py3 (macOS-latest)
  • GitHub Check: flake8-py3 (mypy)
  • GitHub Check: flake8-py3 (codeformat)
  • GitHub Check: quick-py3 (ubuntu-latest)
  • GitHub Check: quick-py3 (windows-latest)
  • GitHub Check: min-dep-pytorch (2.8.0)
  • GitHub Check: min-dep-pytorch (2.7.1)
  • GitHub Check: min-dep-pytorch (2.6.0)
  • GitHub Check: min-dep-py3 (3.12)
  • GitHub Check: min-dep-pytorch (2.5.1)
  • GitHub Check: min-dep-py3 (3.11)
  • GitHub Check: min-dep-py3 (3.9)
  • GitHub Check: min-dep-py3 (3.10)
  • GitHub Check: min-dep-os (ubuntu-latest)
  • GitHub Check: min-dep-os (macOS-latest)
  • GitHub Check: min-dep-os (windows-latest)
🔇 Additional comments (7)
monai/transforms/intensity/array.py (2)

194-212: LGTM!

The _add_noise implementation correctly follows the non-central chi distribution: generates k Gaussian noise components, adds the signal to the first component, then computes the magnitude. Handles both torch and numpy backends.


214-244: LGTM!

The __call__ method correctly handles both channel-wise and global noise application. Type validation for mean/std parameters prevents runtime errors. Implementation is consistent with RandRicianNoise.

tests/transforms/test_rand_noncentralchi_noise.py (1)

29-79: LGTM!

Tests comprehensively verify correctness by replaying the random generation process and comparing against analytically computed expected results. Coverage includes both k=64 (typical MRI) and k=2 (Rician noise special case), with both zero and non-zero mean scenarios.

tests/transforms/test_rand_noncentralchi_noised.py (2)

35-35: Verify dtype choice.

Tests use dtype=np.float64 while the array-based tests use default dtype (float32). Is higher precision intentionally required for dictionary variant testing, or should this match the array tests?


31-73: LGTM!

Tests verify dictionary-based transform correctness by replaying random generation per key and comparing against expected results. Coverage includes multiple keys, both k=64 and k=2, and various mean/std combinations.

monai/transforms/intensity/dictionary.py (2)

241-313: LGTM - Implementation follows correct pattern.

The implementation correctly follows the RandRicianNoised pattern with independent noise per key (line 312), which is appropriate since non-central chi noise generalizes Rician noise. Docstring is comprehensive, type hints are correct, and random state propagation is properly implemented.


2032-2032: Aliases correctly defined.

Follows MONAI naming convention pattern.

karllandheer and others added 2 commits November 2, 2025 12:46
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Signed-off-by: karllandheer <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Signed-off-by: karllandheer <[email protected]>
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 (2)
monai/transforms/intensity/array.py (1)

171-192: Remove redundant assignment.

Line 183 assigns self.prob = prob, but RandomizableTransform.__init__(self, prob) on line 182 already sets this attribute.

     def __init__(
         self,
         prob: float = 0.1,
         mean: Sequence[float] | float = 0.0,
         std: Sequence[float] | float = 1.0,
         degrees_of_freedom: int = 64,  # 64 default because typical modern brain MRI is 32 quadrature coils
         channel_wise: bool = False,
         relative: bool = False,
         sample_std: bool = True,
         dtype: DtypeLike = np.float32,
     ) -> None:
         RandomizableTransform.__init__(self, prob)
-        self.prob = prob
         self.mean = mean
         self.std = std
         if not isinstance(degrees_of_freedom, int) or degrees_of_freedom < 1:
             raise ValueError("degrees_of_freedom must be an integer >= 1.")
         self.degrees_of_freedom = degrees_of_freedom
         self.channel_wise = channel_wise
         self.relative = relative
         self.sample_std = sample_std
         self.dtype = dtype
tests/transforms/test_rand_noncentralchi_noise.py (1)

29-79: Consider testing additional modes.

Current tests validate core correctness well. For more comprehensive coverage, consider adding tests for channel_wise=True and relative=True modes, which have distinct code paths in the implementation.

📜 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 c6b4946 and 20e9189.

📒 Files selected for processing (2)
  • monai/transforms/intensity/array.py (2 hunks)
  • tests/transforms/test_rand_noncentralchi_noise.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:

  • tests/transforms/test_rand_noncentralchi_noise.py
  • monai/transforms/intensity/array.py
🪛 Ruff (0.14.2)
monai/transforms/intensity/array.py

169-169: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


187-187: Avoid specifying long messages outside the exception class

(TRY003)


237-237: Avoid specifying long messages outside the exception class

(TRY003)


239-239: Avoid specifying long messages outside the exception class

(TRY003)


242-242: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (5)
monai/transforms/intensity/array.py (3)

42-44: LGTM - proper module export.

The new transform is correctly added to __all__ with appropriate alphabetical positioning.


194-212: LGTM - correct non-central chi noise implementation.

The method properly implements non-central chi distribution by adding the image to the first of k Gaussian noise channels, then computing the root sum of squares. Handles both Torch and NumPy paths efficiently.


214-244: LGTM - robust implementation with proper validation.

The __call__ method correctly handles both channel-wise and global noise application modes, with appropriate type checking and relative scaling support. The pattern is consistent with similar transforms in the codebase.

tests/transforms/test_rand_noncentralchi_noise.py (2)

29-52: LGTM - thorough correctness validation.

Test properly validates the transform by manually replicating the noise generation with controlled seeding. Checks dtype preservation and numerical correctness with appropriate tolerance.


54-79: LGTM - validates Rician noise special case.

Explicitly testing degrees_of_freedom=2 is valuable since Rician noise is a well-known special case of non-central chi distribution mentioned in the transform documentation.

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

Adds RandNonCentralChiNoise and RandNonCentralChiNoised transforms to generalize Rician noise (k=2) to arbitrary degrees of freedom (k), enabling accurate noise simulation for modern MRI with 32+ quadrature coils.

Key changes:

  • Implements array transform RandNonCentralChiNoise with proper input validation (degrees_of_freedom must be int >= 1)
  • Adds dictionary wrapper RandNonCentralChiNoised following MONAI's established patterns
  • Supports both PyTorch and NumPy backends with proper device handling
  • Includes comprehensive tests verifying mathematical correctness for k=2 (Rician case) and k=64
  • Properly exports new transforms in __init__.py and updates CHANGELOG
  • Follows existing codebase patterns from RandRicianNoise for consistency

The implementation correctly models the non-central chi distribution by generating k Gaussian noise arrays, adding the signal to the first array, and computing the magnitude as sqrt(sum(squares)). The code structure, error handling, and testing approach align well with MONAI conventions.

Confidence Score: 4/5

  • This PR is safe to merge with minor considerations
  • The implementation follows established MONAI patterns closely (mirroring RandRicianNoise), includes proper input validation, comprehensive tests with mathematical verification, and supports both major backends. Score of 4 rather than 5 due to limited test coverage of edge cases (channel_wise, relative, sample_std=False modes not explicitly tested) and the addition of a personal dev directory to .gitignore.
  • No files require special attention - all implementations follow established patterns and include proper validation

Important Files Changed

File Analysis

Filename Score Overview
monai/transforms/intensity/array.py 4/5 Added RandNonCentralChiNoise transform that generalizes Rician noise to k degrees of freedom, with proper validation and support for both PyTorch and NumPy backends
monai/transforms/intensity/dictionary.py 5/5 Added dictionary wrapper RandNonCentralChiNoised following existing MONAI patterns for dictionary transforms
tests/transforms/test_rand_noncentralchi_noise.py 4/5 Comprehensive tests for array transform with zero/non-zero mean cases and k=2 (Rician) validation
tests/transforms/test_rand_noncentralchi_noised.py 4/5 Dictionary transform tests covering multiple keys with proper randomization state verification
.gitignore 5/5 Added monai-dev/ directory to gitignore (developer's local directory)

Sequence Diagram

sequenceDiagram
    participant User
    participant RandNonCentralChiNoised
    participant RandNonCentralChiNoise
    participant RandomState

    User->>RandNonCentralChiNoised: __call__(data_dict)
    RandNonCentralChiNoised->>RandomState: randomize() - check prob
    
    alt transform should apply
        loop for each key in data_dict
            RandNonCentralChiNoised->>RandNonCentralChiNoise: __call__(img, randomize=True)
            RandNonCentralChiNoise->>RandomState: randomize() - check prob
            RandNonCentralChiNoise->>RandNonCentralChiNoise: _add_noise(img, mean, std, k)
            RandNonCentralChiNoise->>RandomState: uniform(0, std) if sample_std
            RandNonCentralChiNoise->>RandomState: normal(mean, std, size=(k, ...))
            Note over RandNonCentralChiNoise: Generate k Gaussian noise arrays
            Note over RandNonCentralChiNoise: Add signal to first array: noises[0] += img
            Note over RandNonCentralChiNoise: Compute sqrt(sum(noises^2))
            RandNonCentralChiNoise-->>RandNonCentralChiNoised: noised_img
        end
        RandNonCentralChiNoised-->>User: noised_data_dict
    else skip transform
        RandNonCentralChiNoised-->>User: original_data_dict
    end
Loading

5 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

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.

1 participant