Skip to content

Conversation

@sogolsahebi
Copy link

@sogolsahebi sogolsahebi commented Dec 1, 2025

Add N4 Bias Correction for MR: N4BiasFieldCorrection transform using SimpleITK’s N4BiasFieldCorrectionImageFilter, applied only for MR images via transformed_image.metadata["Modality"] == "MR".

Summary by CodeRabbit

  • New Features
    • Automatic N4 bias‑field correction for MR scans to reduce intensity inhomogeneities and improve MR image consistency; non‑MR processing remains unchanged.
    • Exposes a new bias‑correction transform so users can run N4 bias‑field correction manually as part of processing pipelines.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

📝 Walkthrough

Walkthrough

Adds an N4BiasFieldCorrection intensity transform and a bias_correction functional wrapper (SimpleITK). Re-exports the class from the transforms package and updates transformer logic to apply N4BiasFieldCorrection only when an image's metadata Modality equals "MR".

Changes

Cohort / File(s) Summary
Intensity transform (new class)
src/imgtools/transforms/intensity_transforms.py
Adds dataclass N4BiasFieldCorrection(IntensityTransform) with __call__(self, image: Image) -> Image that delegates to bias_correction(image). Adds the class to module exports (__all__).
Functional wrapper (SimpleITK integration)
src/imgtools/transforms/functional.py
Adds bias_correction(image) that applies sitk.N4BiasFieldCorrectionImageFilter via SimpleITKFilter, imports SimpleITKFilter, and exposes bias_correction in __all__.
Package re-export
src/imgtools/transforms/__init__.py
Re-exports N4BiasFieldCorrection by importing it from .intensity_transforms and adding it to __all__.
Transformer branch
src/imgtools/transforms/transformer.py
Imports N4BiasFieldCorrection and gates application: when transform is N4BiasFieldCorrection and image.metadata["Modality"] == "MR", apply transform; otherwise retain existing intensity/spatial handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Check safe metadata access (handle missing keys, casing, and use of .get).
  • Verify bias_correction preserves image geometry (spacing/origin/direction) and dtype; confirm SimpleITK parameters and exceptions are handled.
  • Ensure the MR-only conditional doesn't change application order or skip other intensity transforms; add tests for MR vs non‑MR cases.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: adding MRI bias correction functionality via the N4BiasFieldCorrection 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
  • Commit unit tests in branch sogolsahebi/-feat/mri-bias-correction

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3a16e3 and c1e9723.

📒 Files selected for processing (1)
  • src/imgtools/transforms/transformer.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/imgtools/transforms/transformer.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). (8)
  • GitHub Check: Unit-Tests (macos-latest, py312, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py313, public)
  • GitHub Check: Integration-Tests (windows-latest, py313, public)
  • GitHub Check: Integration-Tests (macos-latest, py313, public)
  • GitHub Check: Integration-Tests (macos-latest, py310, public)
  • GitHub Check: Integration-Tests (windows-latest, py310, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py310, public)
  • GitHub Check: Linting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov
Copy link

codecov bot commented Dec 1, 2025

Codecov Report

❌ Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.65%. Comparing base (2fe988a) to head (c1e9723).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/imgtools/transforms/intensity_transforms.py 0.00% 5 Missing ⚠️
src/imgtools/transforms/transformer.py 0.00% 5 Missing ⚠️
src/imgtools/transforms/functional.py 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #432      +/-   ##
==========================================
- Coverage   54.81%   54.65%   -0.16%     
==========================================
  Files          66       66              
  Lines        4302     4314      +12     
==========================================
  Hits         2358     2358              
- Misses       1944     1956      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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: 1

🧹 Nitpick comments (3)
src/imgtools/transforms/transformer.py (1)

66-81: Consider reducing Transformer’s coupling to a specific transform class

The explicit isinstance(transform, N4BiasFieldCorrection) check bakes N4-specific knowledge into Transformer, while other transforms are handled via their base types and capabilities (e.g., supports_reference). Longer term, you might improve maintainability by:

  • Moving the modality gating into N4BiasFieldCorrection.__call__ (or a dedicated supports_modality(metadata) hook), so the transformer doesn’t need to know about concrete subclasses.
  • Or introducing a generic “requires MR modality” capability on transforms that N4 can implement.

This isn’t blocking, but keeping Transformer agnostic to concrete transform types will make future additions easier to integrate and reason about.

src/imgtools/transforms/intensity_transforms.py (2)

13-17: Export N4BiasFieldCorrection via __all__ for consistency and discoverability

The new N4BiasFieldCorrection class is implemented cleanly and its docstrings align with the behavior, but it’s not added to __all__. The other intensity transforms are exported there, so including N4 will make it easier to discover and to re-export from higher-level modules that rely on __all__.

You can align it with the existing pattern like this:

 __all__ = [
     "IntensityTransform",
     "ClipIntensity",
     "WindowIntensity",
+    "N4BiasFieldCorrection",
 ]

This keeps the public API consistent with the rest of the intensity transforms.

Also applies to: 155-180


155-180: Optionally make the internal SimpleITK filter explicit for clarity

Right now, self.filter is created in __post_init__ without being declared on the dataclass, which works but is a bit implicit. If you want to improve readability and help static analysis tools, you could declare it as a non-init, non-repr field:

-from dataclasses import dataclass
+from dataclasses import dataclass, field
@@
-@dataclass
-class N4BiasFieldCorrection(IntensityTransform):
-    """
+@dataclass
+class N4BiasFieldCorrection(IntensityTransform):
+    """
     N4 bias-field correction.
@@
-    
-    def __post_init__(self) -> None:
-        self.filter = SimpleITKFilter(N4BiasFieldCorrectionImageFilter())
+    
+    filter: SimpleITKFilter = field(init=False, repr=False)
+
+    def __post_init__(self) -> None:
+        self.filter = SimpleITKFilter(N4BiasFieldCorrectionImageFilter())

This keeps behavior the same while making the internal state and intent clearer.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6fb32f and ec54a76.

📒 Files selected for processing (2)
  • src/imgtools/transforms/intensity_transforms.py (2 hunks)
  • src/imgtools/transforms/transformer.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

⚙️ CodeRabbit configuration file

Review the Python code for compliance with PEP 8 and PEP 257 (docstring conventions). Ensure the following: - Variables and functions follow meaningful naming conventions. - Docstrings are present, accurate, and align with the implementation. - Code is efficient and avoids redundancy while adhering to DRY principles. - Consider suggestions to enhance readability and maintainability. - Highlight any potential performance issues, edge cases, or logical errors. - Ensure all imported libraries are used and necessary.

Files:

  • src/imgtools/transforms/transformer.py
  • src/imgtools/transforms/intensity_transforms.py
🧬 Code graph analysis (1)
src/imgtools/transforms/transformer.py (1)
src/imgtools/transforms/intensity_transforms.py (1)
  • N4BiasFieldCorrection (157-179)
⏰ 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). (7)
  • GitHub Check: Unit-Tests (macos-latest, py313, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py310, public)
  • GitHub Check: Integration-Tests (windows-latest, py313, public)
  • GitHub Check: Integration-Tests (windows-latest, py310, public)
  • GitHub Check: Integration-Tests (macos-latest, py313, public)
  • GitHub Check: Integration-Tests (macos-latest, py310, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py313, public)

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: 1

Caution

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

⚠️ Outside diff range comments (1)
src/imgtools/transforms/intensity_transforms.py (1)

12-16: Add N4BiasFieldCorrection to the all list.

The new N4BiasFieldCorrection class is a public API addition but is missing from the __all__ export list. This omission means the class won't be exported when using from imgtools.transforms.intensity_transforms import *.

Apply this diff to add the export:

 __all__ = [
     "IntensityTransform",
     "ClipIntensity",
     "WindowIntensity",
+    "N4BiasFieldCorrection",
 ]
🧹 Nitpick comments (1)
src/imgtools/transforms/intensity_transforms.py (1)

164-165: Consider adding configurability for N4 filter parameters.

The current implementation uses default parameters for N4BiasFieldCorrectionImageFilter. While this works well for typical cases, the filter supports configuration options like number of iterations and convergence threshold that may be useful for advanced use cases.

If configurability is desired, consider this enhancement:

 @dataclass
 class N4BiasFieldCorrection(IntensityTransform):
     """N4 bias field correction for MR images.
     ...
+    
+    Parameters
+    ----------
+    number_of_iterations : list of int, optional
+        Number of iterations per resolution level. Default uses filter defaults.
+    convergence_threshold : float, optional
+        Convergence threshold. Default uses filter defaults.
     """
+    number_of_iterations: list[int] | None = None
+    convergence_threshold: float | None = None

     def __post_init__(self) -> None:
-        self.filter = SimpleITKFilter(N4BiasFieldCorrectionImageFilter())
+        n4_filter = N4BiasFieldCorrectionImageFilter()
+        if self.number_of_iterations is not None:
+            n4_filter.SetMaximumNumberOfIterations(self.number_of_iterations)
+        if self.convergence_threshold is not None:
+            n4_filter.SetConvergenceThreshold(self.convergence_threshold)
+        self.filter = SimpleITKFilter(n4_filter)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dc7929 and 57e2ee5.

📒 Files selected for processing (2)
  • src/imgtools/transforms/intensity_transforms.py (2 hunks)
  • src/imgtools/transforms/transformer.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/imgtools/transforms/transformer.py
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

⚙️ CodeRabbit configuration file

Review the Python code for compliance with PEP 8 and PEP 257 (docstring conventions). Ensure the following: - Variables and functions follow meaningful naming conventions. - Docstrings are present, accurate, and align with the implementation. - Code is efficient and avoids redundancy while adhering to DRY principles. - Consider suggestions to enhance readability and maintainability. - Highlight any potential performance issues, edge cases, or logical errors. - Ensure all imported libraries are used and necessary.

Files:

  • src/imgtools/transforms/intensity_transforms.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). (7)
  • GitHub Check: Integration-Tests (windows-latest, py310, public)
  • GitHub Check: Integration-Tests (macos-latest, py313, public)
  • GitHub Check: Integration-Tests (macos-latest, py310, public)
  • GitHub Check: Integration-Tests (windows-latest, py313, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py313, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py310, public)
  • GitHub Check: Linting
🔇 Additional comments (2)
src/imgtools/transforms/intensity_transforms.py (2)

3-3: LGTM! Imports are properly utilized.

Both N4BiasFieldCorrectionImageFilter and SimpleITKFilter are appropriately imported and used in the new N4BiasFieldCorrection class.

Also applies to: 10-10


167-179: LGTM! Clean delegation pattern.

The __call__ method properly delegates to the wrapped filter and includes a well-formatted docstring. The implementation correctly preserves image geometry while applying intensity correction.

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: 1

🧹 Nitpick comments (2)
src/imgtools/transforms/functional.py (1)

418-433: Consider splitting the docstring summary line for PEP 257 compliance.

The first line of the docstring is quite long. PEP 257 recommends a concise one-line summary followed by a blank line before detailed description. The other functions in this file (e.g., clip_intensity, window_intensity) follow this pattern.

 def bias_correction(image: sitk.Image) -> sitk.Image:
-    """Apply N4 bias field correction to reduce smooth intensity inhomogeneities (bias fields) commonly found in
-    MR imaging. This transform corrects voxel intensities while preserving image geometry (spacing, orientation, and dimensions).
+    """Apply N4 bias field correction to an MR image.
+
+    Reduces smooth intensity inhomogeneities (bias fields) commonly found
+    in MR imaging. This transform corrects voxel intensities while preserving
+    image geometry (spacing, orientation, and dimensions).
 
     Parameters
     ----------
src/imgtools/transforms/intensity_transforms.py (1)

177-189: Add blank line between docstring sections for consistency.

The __call__ docstring is missing a blank line between the Parameters and Returns sections, which differs from the formatting style used in other methods in this file.

         image : Image
             The input MR image to apply bias-field correction.
+
         Returns
         -------
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 326c6fd and 7e8be68.

📒 Files selected for processing (3)
  • src/imgtools/transforms/__init__.py (2 hunks)
  • src/imgtools/transforms/functional.py (3 hunks)
  • src/imgtools/transforms/intensity_transforms.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

⚙️ CodeRabbit configuration file

Review the Python code for compliance with PEP 8 and PEP 257 (docstring conventions). Ensure the following: - Variables and functions follow meaningful naming conventions. - Docstrings are present, accurate, and align with the implementation. - Code is efficient and avoids redundancy while adhering to DRY principles. - Consider suggestions to enhance readability and maintainability. - Highlight any potential performance issues, edge cases, or logical errors. - Ensure all imported libraries are used and necessary.

Files:

  • src/imgtools/transforms/__init__.py
  • src/imgtools/transforms/intensity_transforms.py
  • src/imgtools/transforms/functional.py
🧬 Code graph analysis (3)
src/imgtools/transforms/__init__.py (1)
src/imgtools/transforms/intensity_transforms.py (1)
  • N4BiasFieldCorrection (154-189)
src/imgtools/transforms/intensity_transforms.py (1)
src/imgtools/transforms/functional.py (3)
  • bias_correction (418-433)
  • clip_intensity (365-388)
  • window_intensity (391-415)
src/imgtools/transforms/functional.py (1)
src/imgtools/transforms/lambda_transforms.py (1)
  • SimpleITKFilter (11-89)
🔇 Additional comments (4)
src/imgtools/transforms/__init__.py (1)

14-14: LGTM! Clean addition to the public API.

The import and __all__ export follow the existing conventions in this file, maintaining alphabetical order within the import block and logical grouping under the intensity transform section.

Also applies to: 46-46

src/imgtools/transforms/functional.py (1)

4-5: Import and export additions look good.

The SimpleITKFilter import is properly utilized by the new bias_correction function, and the export is correctly added to __all__.

Also applies to: 18-18

src/imgtools/transforms/intensity_transforms.py (2)

153-172: Well-documented class with clear purpose.

The docstring is comprehensive, covering the algorithm reference, computational considerations, and modality suitability. This addresses maintainability well by giving future developers the context they need.


6-6: Import and export properly configured.

The bias_correction import is used by the new class, and the __all__ export is correctly updated.

Also applies to: 12-12

Comment on lines +174 to +176
# def __post_init__(self) -> None:
# self.filter = SimpleITKFilter(N4BiasFieldCorrectionImageFilter())

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove commented-out code.

This commented-out __post_init__ block is dead code. If it's no longer needed, it should be removed to keep the codebase clean. If it's a placeholder for future work, consider opening an issue to track it instead.

-    # def __post_init__(self) -> None:
-    #     self.filter = SimpleITKFilter(N4BiasFieldCorrectionImageFilter())
-
     def __call__(self, image: Image) -> Image:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# def __post_init__(self) -> None:
# self.filter = SimpleITKFilter(N4BiasFieldCorrectionImageFilter())
def __call__(self, image: Image) -> Image:
🤖 Prompt for AI Agents
In src/imgtools/transforms/intensity_transforms.py around lines 174 to 176,
remove the commented-out __post_init__ block (the two lines that instantiate
SimpleITKFilter with N4BiasFieldCorrectionImageFilter) because it is dead code;
if the intent was to keep it as a reminder, either open a tracking issue or add
a TODO with a link to that issue, otherwise simply delete the commented lines to
clean up the codebase.

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)
src/imgtools/transforms/transformer.py (1)

96-113: Plain sitk.Image path returns an undefined name and may never be reachable

Unrelated to the new N4 logic, but for maintainability it’s worth noting:

  • In the else branch (intended for plain sitk.Image), you return result, which is only defined in the preceding VectorMask / MedImage branches, so this would raise a NameError if ever hit.
  • Combined with the N4 gating (and other transform logic) that accesses transformed_image.metadata, a plain sitk.Image without metadata would already error earlier in the pipeline.

If you intend to support raw sitk.Image here, consider:

-            else:
-                # For plain sitk.Image, just return the transformed image
-                return result
+            else:
+                # For plain sitk.Image, just return the transformed image
+                return transformed_image

and pairing that with the more defensive metadata access suggested above. If raw sitk.Image isn’t a supported input, simplifying this branch and tightening the type hints would also improve clarity.

🧹 Nitpick comments (1)
src/imgtools/transforms/transformer.py (1)

14-14: N4 MR-only gating works but can be made more robust and readable

Nice job restricting N4BiasFieldCorrection to MR by checking Modality == "MR". To make this more maintainable and defensive, consider:

  • Guarding metadata access with getattr(..., "metadata", {}) or {} so this doesn’t blow up if a transform ever yields an object without a metadata dict.
  • Pulling out modality = metadata.get("Modality") for clarity and logging a skip when modality isn’t MR.
  • Avoiding the nested if by early-continue for non‑MR N4, then unifying the call site so both N4 (when MR) and other intensity/spatial transforms go through the same transform(transformed_image) path.

For example:

-                elif isinstance(
-                    transform, (IntensityTransform, SpatialTransform)
-                ):
-                    # Apply N4BiasFieldCorrection only for MR images
-                    if isinstance(transform, N4BiasFieldCorrection):
-                        if (
-                            transformed_image.metadata.get(
-                                "Modality", "Unknown"
-                            )
-                            == "MR"
-                        ):
-                            transformed_image = transform(transformed_image) # type: ignore
-                    else:
-                        transformed_image = transform(transformed_image)
+                elif isinstance(
+                    transform, (IntensityTransform, SpatialTransform)
+                ):
+                    if isinstance(transform, N4BiasFieldCorrection):
+                        metadata = getattr(transformed_image, "metadata", {}) or {}
+                        modality = metadata.get("Modality")
+                        if modality != "MR":
+                            logger.debug(
+                                "Skipping N4BiasFieldCorrection for non-MR modality %r",
+                                modality,
+                            )
+                            continue
+
+                    transformed_image = transform(transformed_image)  # type: ignore

This keeps the MR-only behavior, improves resilience to edge cases, and simplifies the control flow a bit.

Also applies to: 72-85

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e8be68 and c3a16e3.

📒 Files selected for processing (1)
  • src/imgtools/transforms/transformer.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

⚙️ CodeRabbit configuration file

Review the Python code for compliance with PEP 8 and PEP 257 (docstring conventions). Ensure the following: - Variables and functions follow meaningful naming conventions. - Docstrings are present, accurate, and align with the implementation. - Code is efficient and avoids redundancy while adhering to DRY principles. - Consider suggestions to enhance readability and maintainability. - Highlight any potential performance issues, edge cases, or logical errors. - Ensure all imported libraries are used and necessary.

Files:

  • src/imgtools/transforms/transformer.py
🧬 Code graph analysis (1)
src/imgtools/transforms/transformer.py (1)
src/imgtools/transforms/intensity_transforms.py (1)
  • N4BiasFieldCorrection (154-189)
⏰ 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). (7)
  • GitHub Check: Unit-Tests (macos-latest, py313, public)
  • GitHub Check: Integration-Tests (macos-latest, py310, public)
  • GitHub Check: Integration-Tests (windows-latest, py313, public)
  • GitHub Check: Integration-Tests (macos-latest, py313, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py313, public)
  • GitHub Check: Integration-Tests (windows-latest, py310, public)
  • GitHub Check: Integration-Tests (ubuntu-latest, py310, public)

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.

4 participants